From d000d5cacac42cdb6aa77ee3e23f7e2a5688abae Mon Sep 17 00:00:00 2001 From: Tomoko Uchida Date: Tue, 4 Dec 2018 22:53:28 +0900 Subject: [PATCH 01/12] Add the Luke module --- dev-tools/idea/.idea/ant.xml | 1 + dev-tools/idea/.idea/modules.xml | 1 + dev-tools/idea/.idea/workspace.xml | 55 +- dev-tools/idea/lucene/luke/luke.iml | 21 + lucene/build.xml | 2 + lucene/ivy-ignore-conflicts.properties | 2 +- lucene/ivy-versions.properties | 10 + lucene/luke/bin/luke.bat | 18 + lucene/luke/bin/luke.sh | 23 + lucene/luke/build.xml | 77 ++ lucene/luke/ivy.xml | 45 + lucene/luke/log4j2.xml | 50 + .../lucene/luke/app/AbstractHandler.java | 47 + .../lucene/luke/app/DirectoryHandler.java | 105 ++ .../lucene/luke/app/DirectoryObserver.java | 27 + .../apache/lucene/luke/app/IndexHandler.java | 140 ++ .../apache/lucene/luke/app/IndexObserver.java | 27 + .../apache/lucene/luke/app/LukeModule.java | 49 + .../org/apache/lucene/luke/app/LukeState.java | 57 + .../org/apache/lucene/luke/app/Observer.java | 22 + .../luke/app/desktop/DesktopModule.java | 147 +++ .../lucene/luke/app/desktop/LukeMain.java | 87 ++ .../luke/app/desktop/MessageBroker.java | 59 + .../lucene/luke/app/desktop/Preferences.java | 69 + .../luke/app/desktop/PreferencesImpl.java | 145 +++ .../components/AnalysisPanelProvider.java | 440 +++++++ .../components/AnalysisTabOperator.java | 33 + .../components/CommitsPanelProvider.java | 583 +++++++++ .../components/ComponentOperatorRegistry.java | 44 + .../components/DocumentsPanelProvider.java | 1123 +++++++++++++++++ .../components/DocumentsTabOperator.java | 31 + .../desktop/components/LogsPanelProvider.java | 67 + .../components/LukeWindowOperator.java | 25 + .../components/LukeWindowProvider.java | 253 ++++ .../desktop/components/MenuBarProvider.java | 295 +++++ .../components/OverviewPanelProvider.java | 653 ++++++++++ .../components/SearchPanelProvider.java | 833 ++++++++++++ .../desktop/components/SearchTabOperator.java | 25 + .../desktop/components/TabSwitcherProxy.java | 46 + .../components/TabbedPaneProvider.java | 148 +++ .../desktop/components/TableColumnInfo.java | 33 + .../desktop/components/TableModelBase.java | 75 ++ .../dialog/ConfirmDialogFactory.java | 28 + .../dialog/ConfirmDialogFactoryImpl.java | 111 ++ .../components/dialog/HelpDialogFactory.java | 29 + .../dialog/HelpDialogFactoryImpl.java | 98 ++ .../analysis/AnalysisChainDialogFactory.java | 26 + .../AnalysisChainDialogFactoryImpl.java | 148 +++ .../analysis/EditFiltersDialogFactory.java | 33 + .../EditFiltersDialogFactoryImpl.java | 300 +++++ .../dialog/analysis/EditFiltersMode.java | 23 + .../analysis/EditParamsDialogFactory.java | 36 + .../analysis/EditParamsDialogFactoryImpl.java | 248 ++++ .../dialog/analysis/EditParamsMode.java | 23 + .../analysis/TokenAttributeDialogFactory.java | 30 + .../TokenAttributeDialogFactoryImpl.java | 188 +++ .../dialog/analysis/package-info.java | 19 + .../documents/AddDocumentDialogFactory.java | 24 + .../AddDocumentDialogFactoryImpl.java | 587 +++++++++ .../documents/AddDocumentDialogOperator.java | 27 + .../documents/DocValuesDialogFactory.java | 26 + .../documents/DocValuesDialogFactoryImpl.java | 286 +++++ .../documents/IndexOptionsDialogFactory.java | 26 + .../IndexOptionsDialogFactoryImpl.java | 298 +++++ .../documents/StoredValueDialogFactory.java | 27 + .../StoredValueDialogFactoryImpl.java | 124 ++ .../documents/TermVectorDialogFactory.java | 30 + .../TermVectorDialogFactoryImpl.java | 182 +++ .../dialog/documents/package-info.java | 19 + .../dialog/menubar/AboutDialogFactory.java | 24 + .../menubar/AboutDialogFactoryImpl.java | 180 +++ .../menubar/CheckIndexDialogFactory.java | 24 + .../menubar/CheckIndexDialogFactoryImpl.java | 377 ++++++ .../menubar/OpenIndexDialogFactory.java | 24 + .../menubar/OpenIndexDialogFactoryImpl.java | 362 ++++++ .../menubar/OptimizeIndexDialogFactory.java | 24 + .../OptimizeIndexDialogFactoryImpl.java | 256 ++++ .../dialog/menubar/package-info.java | 19 + .../components/dialog/package-info.java | 19 + .../dialog/search/ExplainDialogFactory.java | 28 + .../search/ExplainDialogFactoryImpl.java | 174 +++ .../dialog/search/package-info.java | 19 + .../analysis/CustomAnalyzerPanelOperator.java | 45 + .../analysis/CustomAnalyzerPanelProvider.java | 758 +++++++++++ .../analysis/PresetAnalyzerPanelOperator.java | 30 + .../analysis/PresetAnalyzerPanelProvider.java | 98 ++ .../fragments/analysis/package-info.java | 19 + .../components/fragments/package-info.java | 19 + .../search/AnalyzerPaneProvider.java | 205 +++ .../fragments/search/AnalyzerTabOperator.java | 27 + .../search/FieldValuesPaneProvider.java | 211 ++++ .../search/FieldValuesTabOperator.java | 30 + .../fragments/search/MLTPaneProvider.java | 308 +++++ .../fragments/search/MLTTabOperator.java | 33 + .../search/QueryParserPaneProvider.java | 518 ++++++++ .../search/QueryParserTabOperator.java | 35 + .../search/SimilarityPaneProvider.java | 149 +++ .../search/SimilarityTabOperator.java | 26 + .../fragments/search/SortPaneProvider.java | 238 ++++ .../fragments/search/SortTabOperator.java | 34 + .../fragments/search/package-info.java | 19 + .../app/desktop/components/package-info.java | 19 + .../app/desktop/dto/documents/NewField.java | 139 ++ .../desktop/dto/documents/package-info.java | 19 + .../lucene/luke/app/desktop/package-info.java | 19 + .../luke/app/desktop/util/DialogOpener.java | 52 + .../app/desktop/util/ExceptionHandler.java | 44 + .../luke/app/desktop/util/FontUtils.java | 69 + .../app/desktop/util/HelpHeaderRenderer.java | 129 ++ .../luke/app/desktop/util/ImageUtils.java | 43 + .../luke/app/desktop/util/ListUtils.java | 43 + .../luke/app/desktop/util/MessageUtils.java | 59 + .../luke/app/desktop/util/NumericUtils.java | 105 ++ .../luke/app/desktop/util/StyleConstants.java | 43 + .../luke/app/desktop/util/TabUtils.java | 40 + .../luke/app/desktop/util/TableUtils.java | 85 ++ .../app/desktop/util/TextAreaAppender.java | 91 ++ .../app/desktop/util/TextAreaPrintStream.java | 67 + .../luke/app/desktop/util/URLLabel.java | 65 + .../luke/app/desktop/util/lang/Callable.java | 24 + .../app/desktop/util/lang/package-info.java | 19 + .../luke/app/desktop/util/package-info.java | 19 + .../apache/lucene/luke/app/package-info.java | 19 + .../lucene/luke/models/LukeException.java | 35 + .../apache/lucene/luke/models/LukeModel.java | 67 + .../lucene/luke/models/analysis/Analysis.java | 154 +++ .../luke/models/analysis/AnalysisFactory.java | 27 + .../luke/models/analysis/AnalysisImpl.java | 220 ++++ .../models/analysis/CustomAnalyzerConfig.java | 128 ++ .../luke/models/analysis/package-info.java | 19 + .../lucene/luke/models/commits/Commit.java | 68 + .../lucene/luke/models/commits/Commits.java | 89 ++ .../luke/models/commits/CommitsFactory.java | 34 + .../luke/models/commits/CommitsImpl.java | 225 ++++ .../lucene/luke/models/commits/File.java | 52 + .../lucene/luke/models/commits/Segment.java | 95 ++ .../luke/models/commits/package-info.java | 19 + .../luke/models/documents/DocValues.java | 85 ++ .../models/documents/DocValuesAdapter.java | 168 +++ .../luke/models/documents/DocumentField.java | 166 +++ .../luke/models/documents/Documents.java | 144 +++ .../models/documents/DocumentsFactory.java | 29 + .../luke/models/documents/DocumentsImpl.java | 344 +++++ .../luke/models/documents/TermPosting.java | 90 ++ .../models/documents/TermVectorEntry.java | 174 +++ .../models/documents/TermVectorsAdapter.java | 71 ++ .../luke/models/documents/package-info.java | 19 + .../lucene/luke/models/overview/Overview.java | 121 ++ .../luke/models/overview/OverviewFactory.java | 29 + .../luke/models/overview/OverviewImpl.java | 170 +++ .../luke/models/overview/TermCounts.java | 80 ++ .../luke/models/overview/TermCountsOrder.java | 43 + .../luke/models/overview/TermStats.java | 76 ++ .../lucene/luke/models/overview/TopTerms.java | 68 + .../luke/models/overview/package-info.java | 19 + .../lucene/luke/models/package-info.java | 19 + .../lucene/luke/models/search/MLTConfig.java | 96 ++ .../luke/models/search/QueryParserConfig.java | 251 ++++ .../lucene/luke/models/search/Search.java | 156 +++ .../luke/models/search/SearchFactory.java | 29 + .../lucene/luke/models/search/SearchImpl.java | 451 +++++++ .../luke/models/search/SearchResults.java | 157 +++ .../luke/models/search/SimilarityConfig.java | 100 ++ .../luke/models/search/package-info.java | 19 + .../lucene/luke/models/tools/IndexTools.java | 91 ++ .../luke/models/tools/IndexToolsFactory.java | 34 + .../luke/models/tools/IndexToolsImpl.java | 171 +++ .../luke/models/tools/package-info.java | 19 + .../lucene/luke/models/util/IndexUtils.java | 502 ++++++++ .../lucene/luke/models/util/package-info.java | 19 + .../org/apache/lucene/luke/package-info.java | 19 + .../lucene/luke/util/BytesRefUtils.java | 37 + .../apache/lucene/luke/util/package-info.java | 19 + lucene/luke/src/java/overview.html | 26 + .../luke/src/resources/font/ElegantIcons.ttf | Bin 0 -> 59388 bytes lucene/luke/src/resources/img/indicator.gif | Bin 0 -> 673 bytes lucene/luke/src/resources/img/lucene-logo.gif | Bin 0 -> 1337 bytes lucene/luke/src/resources/img/lucene.gif | Bin 0 -> 335 bytes lucene/luke/src/resources/img/luke-logo.gif | Bin 0 -> 2408 bytes lucene/luke/src/resources/log4j2.xml | 50 + lucene/luke/src/resources/messages.properties | 268 ++++ lucene/luke/src/test/log4j2.xml | 28 + .../models/analysis/AnalysisImplTest.java | 129 ++ .../models/analysis/AnalysisTestBase.java | 23 + .../luke/models/commits/CommitsImplTest.java | 214 ++++ .../documents/DocValuesAdapterTest.java | 114 ++ .../models/documents/DocumentsImplTest.java | 248 ++++ .../models/documents/DocumentsTestBase.java | 152 +++ .../documents/TermVectorsAdapterTest.java | 133 ++ .../models/overview/OverviewImplTest.java | 140 ++ .../models/overview/OverviewTestBase.java | 94 ++ .../luke/models/overview/TermCountsTest.java | 82 ++ .../luke/models/overview/TopTermsTest.java | 40 + .../luke/models/search/SearchImplTest.java | 381 ++++++ lucene/module-build.xml | 11 + lucene/tools/junit4/tests.policy | 6 +- 196 files changed, 21964 insertions(+), 25 deletions(-) create mode 100644 dev-tools/idea/lucene/luke/luke.iml create mode 100644 lucene/luke/bin/luke.bat create mode 100755 lucene/luke/bin/luke.sh create mode 100644 lucene/luke/build.xml create mode 100644 lucene/luke/ivy.xml create mode 100644 lucene/luke/log4j2.xml create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/AbstractHandler.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/DirectoryHandler.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/DirectoryObserver.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/IndexHandler.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/IndexObserver.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/LukeModule.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/LukeState.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/Observer.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/DesktopModule.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/LukeMain.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/MessageBroker.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/Preferences.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/PreferencesImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/AnalysisPanelProvider.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/AnalysisTabOperator.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/CommitsPanelProvider.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/ComponentOperatorRegistry.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/DocumentsPanelProvider.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/DocumentsTabOperator.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LogsPanelProvider.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LukeWindowOperator.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LukeWindowProvider.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/MenuBarProvider.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/OverviewPanelProvider.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/SearchPanelProvider.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/SearchTabOperator.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TabSwitcherProxy.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TabbedPaneProvider.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TableColumnInfo.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TableModelBase.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/ConfirmDialogFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/ConfirmDialogFactoryImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/HelpDialogFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/HelpDialogFactoryImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/AnalysisChainDialogFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/AnalysisChainDialogFactoryImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditFiltersDialogFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditFiltersDialogFactoryImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditFiltersMode.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditParamsDialogFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditParamsDialogFactoryImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditParamsMode.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/TokenAttributeDialogFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/TokenAttributeDialogFactoryImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/AddDocumentDialogFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/AddDocumentDialogFactoryImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/AddDocumentDialogOperator.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/DocValuesDialogFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/DocValuesDialogFactoryImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/IndexOptionsDialogFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/IndexOptionsDialogFactoryImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/StoredValueDialogFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/StoredValueDialogFactoryImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/TermVectorDialogFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/TermVectorDialogFactoryImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/AboutDialogFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/AboutDialogFactoryImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/CheckIndexDialogFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/CheckIndexDialogFactoryImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OpenIndexDialogFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OpenIndexDialogFactoryImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OptimizeIndexDialogFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OptimizeIndexDialogFactoryImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/search/ExplainDialogFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/search/ExplainDialogFactoryImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/search/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/analysis/CustomAnalyzerPanelOperator.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/analysis/CustomAnalyzerPanelProvider.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/analysis/PresetAnalyzerPanelOperator.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/analysis/PresetAnalyzerPanelProvider.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/analysis/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/AnalyzerPaneProvider.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/AnalyzerTabOperator.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/FieldValuesPaneProvider.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/FieldValuesTabOperator.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/MLTPaneProvider.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/MLTTabOperator.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/QueryParserPaneProvider.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/QueryParserTabOperator.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/SimilarityPaneProvider.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/SimilarityTabOperator.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/SortPaneProvider.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/SortTabOperator.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/dto/documents/NewField.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/dto/documents/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/DialogOpener.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/ExceptionHandler.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/FontUtils.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/HelpHeaderRenderer.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/ImageUtils.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/ListUtils.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/MessageUtils.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/NumericUtils.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/StyleConstants.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TabUtils.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TableUtils.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TextAreaAppender.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TextAreaPrintStream.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/URLLabel.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/lang/Callable.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/lang/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/app/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/LukeException.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/LukeModel.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/analysis/Analysis.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/analysis/AnalysisFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/analysis/AnalysisImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/analysis/CustomAnalyzerConfig.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/analysis/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/commits/Commit.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/commits/Commits.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/commits/CommitsFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/commits/CommitsImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/commits/File.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/commits/Segment.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/commits/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocValues.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocValuesAdapter.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocumentField.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/documents/Documents.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocumentsFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocumentsImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermPosting.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermVectorEntry.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermVectorsAdapter.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/documents/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/overview/Overview.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/overview/OverviewFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/overview/OverviewImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermCounts.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermCountsOrder.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermStats.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/overview/TopTerms.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/overview/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/search/MLTConfig.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/search/QueryParserConfig.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/search/Search.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchResults.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/search/SimilarityConfig.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/search/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexTools.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexToolsFactory.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexToolsImpl.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/tools/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/util/IndexUtils.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/models/util/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/package-info.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/util/BytesRefUtils.java create mode 100644 lucene/luke/src/java/org/apache/lucene/luke/util/package-info.java create mode 100644 lucene/luke/src/java/overview.html create mode 100644 lucene/luke/src/resources/font/ElegantIcons.ttf create mode 100644 lucene/luke/src/resources/img/indicator.gif create mode 100755 lucene/luke/src/resources/img/lucene-logo.gif create mode 100755 lucene/luke/src/resources/img/lucene.gif create mode 100755 lucene/luke/src/resources/img/luke-logo.gif create mode 100644 lucene/luke/src/resources/log4j2.xml create mode 100644 lucene/luke/src/resources/messages.properties create mode 100644 lucene/luke/src/test/log4j2.xml create mode 100644 lucene/luke/src/test/org/apache/lucene/luke/models/analysis/AnalysisImplTest.java create mode 100644 lucene/luke/src/test/org/apache/lucene/luke/models/analysis/AnalysisTestBase.java create mode 100644 lucene/luke/src/test/org/apache/lucene/luke/models/commits/CommitsImplTest.java create mode 100644 lucene/luke/src/test/org/apache/lucene/luke/models/documents/DocValuesAdapterTest.java create mode 100644 lucene/luke/src/test/org/apache/lucene/luke/models/documents/DocumentsImplTest.java create mode 100644 lucene/luke/src/test/org/apache/lucene/luke/models/documents/DocumentsTestBase.java create mode 100644 lucene/luke/src/test/org/apache/lucene/luke/models/documents/TermVectorsAdapterTest.java create mode 100644 lucene/luke/src/test/org/apache/lucene/luke/models/overview/OverviewImplTest.java create mode 100644 lucene/luke/src/test/org/apache/lucene/luke/models/overview/OverviewTestBase.java create mode 100644 lucene/luke/src/test/org/apache/lucene/luke/models/overview/TermCountsTest.java create mode 100644 lucene/luke/src/test/org/apache/lucene/luke/models/overview/TopTermsTest.java create mode 100644 lucene/luke/src/test/org/apache/lucene/luke/models/search/SearchImplTest.java diff --git a/dev-tools/idea/.idea/ant.xml b/dev-tools/idea/.idea/ant.xml index 229d83203c65..d3f96556df8c 100644 --- a/dev-tools/idea/.idea/ant.xml +++ b/dev-tools/idea/.idea/ant.xml @@ -24,6 +24,7 @@ + diff --git a/dev-tools/idea/.idea/modules.xml b/dev-tools/idea/.idea/modules.xml index 65b57fb03d5f..4974f19668e9 100644 --- a/dev-tools/idea/.idea/modules.xml +++ b/dev-tools/idea/.idea/modules.xml @@ -30,6 +30,7 @@ + diff --git a/dev-tools/idea/.idea/workspace.xml b/dev-tools/idea/.idea/workspace.xml index 6a1fd0ad8795..970d06eeac44 100644 --- a/dev-tools/idea/.idea/workspace.xml +++ b/dev-tools/idea/.idea/workspace.xml @@ -148,6 +148,14 @@ + + + + + - + @@ -352,28 +360,29 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev-tools/idea/lucene/luke/luke.iml b/dev-tools/idea/lucene/luke/luke.iml new file mode 100644 index 000000000000..f5bac8cd37bb --- /dev/null +++ b/dev-tools/idea/lucene/luke/luke.iml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/lucene/build.xml b/lucene/build.xml index 3c1439c7e264..e3cf905c9713 100644 --- a/lucene/build.xml +++ b/lucene/build.xml @@ -287,6 +287,7 @@ + @@ -310,6 +311,7 @@ + diff --git a/lucene/ivy-ignore-conflicts.properties b/lucene/ivy-ignore-conflicts.properties index 6300bdf6d6fe..55afbc51de94 100644 --- a/lucene/ivy-ignore-conflicts.properties +++ b/lucene/ivy-ignore-conflicts.properties @@ -9,5 +9,5 @@ # where each is an indirect dependency version to ignore (i.e., not # trigger a conflict) when the ant check-lib-versions target is run. -/com.google.guava/guava = 16.0.1 +/com.google.guava/guava = 19.0 /org.ow2.asm/asm = 5.0_BETA \ No newline at end of file diff --git a/lucene/ivy-versions.properties b/lucene/ivy-versions.properties index 2cfb65c0d834..aa1f158f6f64 100644 --- a/lucene/ivy-versions.properties +++ b/lucene/ivy-versions.properties @@ -25,8 +25,13 @@ com.fasterxml.jackson.core.version = 2.9.6 /com.github.ben-manes.caffeine/caffeine = 2.4.0 /com.github.virtuald/curvesapi = 1.04 +com.google.code.findbugs.version = 3.0.1 +/com.google.code.findbugs/jsr305 = ${com.google.code.findbugs.version} + /com.google.guava/guava = 14.0.1 +/com.google.inject/guice = 4.1.0 + /com.google.protobuf/protobuf-java = 3.1.0 /com.googlecode.juniversalchardet/juniversalchardet = 1.0.3 /com.googlecode.mp4parser/isoparser = 1.1.22 @@ -265,6 +270,9 @@ org.gagravarr.vorbis.java.version = 0.8 /org.gagravarr/vorbis-java-tika = ${org.gagravarr.vorbis.java.version} /org.hsqldb/hsqldb = 2.4.0 + +/org.ini4j/ini4j = 0.5.4 + /org.jdom/jdom2 = 2.0.6 /org.jsoup/jsoup = 1.11.3 @@ -286,6 +294,8 @@ org.ow2.asm.version = 5.1 /org.ow2.asm/asm = ${org.ow2.asm.version} /org.ow2.asm/asm-commons = ${org.ow2.asm.version} +/org.reflections/reflections = 0.9.8 + org.restlet.jee.version = 2.3.0 /org.restlet.jee/org.restlet = ${org.restlet.jee.version} /org.restlet.jee/org.restlet.ext.servlet = ${org.restlet.jee.version} diff --git a/lucene/luke/bin/luke.bat b/lucene/luke/bin/luke.bat new file mode 100644 index 000000000000..c67357cc97a3 --- /dev/null +++ b/lucene/luke/bin/luke.bat @@ -0,0 +1,18 @@ +@echo off +@setlocal enabledelayedexpansion + +set ADD_OPENS_OPTION=--add-opens java.base/java.lang=ALL-UNNAMED +java %ADD_OPENS_OPTION% -version > nul 2>&1 +if %errorlevel% equ 0 ( + rem running on jdk9+ + set JAVA_OPTIONS=%ADD_OPENS_OPTION% +) + +set JAVA_OPTIONS=%JAVA_OPTIONS% -Xmx1024m -Xms512m -XX:MaxMetaspaceSize=256m + +set CLASSPATHS=.\*;.\lib\*;..\core\*;..\codecs\*;..\backward-codecs\*;..\queries\*;..\queryparser\*;..\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 000000000000..8859a5ebb182 --- /dev/null +++ b/lucene/luke/bin/luke.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +ADD_OPENS_OPTION="--add-opens java.base/java.lang=ALL-UNNAMED" +java ${ADD_OPENS_OPTION} -version > /dev/null 2>&1 +if [[ $? -eq 0 ]]; then + # running on jdk9+ + JAVA_OPTIONS="${ADD_OPENS_OPTION}" +else + # running on jdk8 + JAVA_OPTIONS="" +fi + +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/*:../misc/*" +for dir in `ls ../analysis`; do + CLASSPATHS="${CLASSPATHS}:../analysis/${dir}/*:../analysis/${dir}/lib/*" +done + +nohup java -cp ${CLASSPATHS} ${JAVA_OPTIONS} org.apache.lucene.luke.app.desktop.LukeMain > ${HOME}/.luke.d/luke_out.log 2>&1 & diff --git a/lucene/luke/build.xml b/lucene/luke/build.xml new file mode 100644 index 000000000000..8908bc158378 --- /dev/null +++ b/lucene/luke/build.xml @@ -0,0 +1,77 @@ + + + + + + + + Luke - Lucene Toolbox + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lucene/luke/ivy.xml b/lucene/luke/ivy.xml new file mode 100644 index 000000000000..a1d32f959787 --- /dev/null +++ b/lucene/luke/ivy.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lucene/luke/log4j2.xml b/lucene/luke/log4j2.xml new file mode 100644 index 000000000000..f65c06edfd66 --- /dev/null +++ b/lucene/luke/log4j2.xml @@ -0,0 +1,50 @@ + + + + + + + [%d{ISO8601}] %5p (%F:%L) - %m%n + + + + + + + + false + + + + + + + + + + + + + + + + + + + + \ No newline at end of file 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 000000000000..786ec4d1cc0f --- /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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Abstract handler */ +public abstract class AbstractHandler { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private List 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 000000000000..2cbe46468929 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/DirectoryHandler.java @@ -0,0 +1,105 @@ +/* + * 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 javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; + +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 { + + private LukeStateImpl state; + + @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(@Nonnull String indexPath, @Nullable String dirImpl) { + 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; + } + + 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 000000000000..64371150f879 --- /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 000000000000..8aee64c16983 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/IndexHandler.java @@ -0,0 +1,140 @@ +/* + * 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 javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.lang.invoke.MethodHandles; + +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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Index open/close handler */ +public final class IndexHandler extends AbstractHandler { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private LukeStateImpl state; + + @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(@Nonnull String indexPath, @Nullable String dirImpl) { + open(indexPath, dirImpl, false, false, false); + } + + public void open(@Nonnull String indexPath, @Nullable String dirImpl, boolean readOnly, boolean useCompound, boolean keepAllCommits) { + 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; + } + + 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 000000000000..599b1090c4dd --- /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/LukeModule.java b/lucene/luke/src/java/org/apache/lucene/luke/app/LukeModule.java new file mode 100644 index 000000000000..3e9bb0792a05 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/LukeModule.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; + +import com.google.inject.AbstractModule; +import com.google.inject.Singleton; +import org.apache.lucene.luke.app.desktop.Preferences; +import org.apache.lucene.luke.app.desktop.PreferencesImpl; +import org.apache.lucene.luke.models.analysis.AnalysisFactory; +import org.apache.lucene.luke.models.commits.CommitsFactory; +import org.apache.lucene.luke.models.documents.DocumentsFactory; +import org.apache.lucene.luke.models.overview.OverviewFactory; +import org.apache.lucene.luke.models.search.SearchFactory; +import org.apache.lucene.luke.models.tools.IndexToolsFactory; + +/** Guice configuration */ +public final class LukeModule extends AbstractModule { + + @Override + protected void configure() { + bind(OverviewFactory.class).toInstance(new OverviewFactory()); + bind(DocumentsFactory.class).toInstance(new DocumentsFactory()); + bind(SearchFactory.class).toInstance(new SearchFactory()); + bind(AnalysisFactory.class).toInstance(new AnalysisFactory()); + bind(CommitsFactory.class).toInstance(new CommitsFactory()); + bind(IndexToolsFactory.class).toInstance(new IndexToolsFactory()); + + bind(DirectoryHandler.class).toInstance(new DirectoryHandler()); + bind(IndexHandler.class).toInstance(new IndexHandler()); + + bind(Preferences.class).to(PreferencesImpl.class).in(Singleton.class); + } + +} 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 000000000000..1d2fff99508c --- /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 of index/directory status. + */ +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 000000000000..290865b8986d --- /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/DesktopModule.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/DesktopModule.java new file mode 100644 index 000000000000..9d7420647f03 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/DesktopModule.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.desktop; + +import javax.swing.JFrame; +import javax.swing.JMenuBar; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTabbedPane; +import javax.swing.JTextArea; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.name.Names; +import org.apache.lucene.luke.app.LukeModule; +import org.apache.lucene.luke.app.desktop.components.AnalysisPanelProvider; +import org.apache.lucene.luke.app.desktop.components.CommitsPanelProvider; +import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry; +import org.apache.lucene.luke.app.desktop.components.DocumentsPanelProvider; +import org.apache.lucene.luke.app.desktop.components.LogsPanelProvider; +import org.apache.lucene.luke.app.desktop.components.LukeWindowProvider; +import org.apache.lucene.luke.app.desktop.components.MenuBarProvider; +import org.apache.lucene.luke.app.desktop.components.OverviewPanelProvider; +import org.apache.lucene.luke.app.desktop.components.SearchPanelProvider; +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.dialog.ConfirmDialogFactory; +import org.apache.lucene.luke.app.desktop.components.dialog.ConfirmDialogFactoryImpl; +import org.apache.lucene.luke.app.desktop.components.dialog.HelpDialogFactory; +import org.apache.lucene.luke.app.desktop.components.dialog.HelpDialogFactoryImpl; +import org.apache.lucene.luke.app.desktop.components.dialog.analysis.AnalysisChainDialogFactory; +import org.apache.lucene.luke.app.desktop.components.dialog.analysis.AnalysisChainDialogFactoryImpl; +import org.apache.lucene.luke.app.desktop.components.dialog.analysis.EditFiltersDialogFactory; +import org.apache.lucene.luke.app.desktop.components.dialog.analysis.EditFiltersDialogFactoryImpl; +import org.apache.lucene.luke.app.desktop.components.dialog.analysis.EditParamsDialogFactory; +import org.apache.lucene.luke.app.desktop.components.dialog.analysis.EditParamsDialogFactoryImpl; +import org.apache.lucene.luke.app.desktop.components.dialog.analysis.TokenAttributeDialogFactory; +import org.apache.lucene.luke.app.desktop.components.dialog.analysis.TokenAttributeDialogFactoryImpl; +import org.apache.lucene.luke.app.desktop.components.dialog.documents.AddDocumentDialogFactory; +import org.apache.lucene.luke.app.desktop.components.dialog.documents.AddDocumentDialogFactoryImpl; +import org.apache.lucene.luke.app.desktop.components.dialog.documents.DocValuesDialogFactory; +import org.apache.lucene.luke.app.desktop.components.dialog.documents.DocValuesDialogFactoryImpl; +import org.apache.lucene.luke.app.desktop.components.dialog.documents.IndexOptionsDialogFactory; +import org.apache.lucene.luke.app.desktop.components.dialog.documents.IndexOptionsDialogFactoryImpl; +import org.apache.lucene.luke.app.desktop.components.dialog.documents.StoredValueDialogFactory; +import org.apache.lucene.luke.app.desktop.components.dialog.documents.StoredValueDialogFactoryImpl; +import org.apache.lucene.luke.app.desktop.components.dialog.documents.TermVectorDialogFactory; +import org.apache.lucene.luke.app.desktop.components.dialog.documents.TermVectorDialogFactoryImpl; +import org.apache.lucene.luke.app.desktop.components.dialog.menubar.AboutDialogFactory; +import org.apache.lucene.luke.app.desktop.components.dialog.menubar.AboutDialogFactoryImpl; +import org.apache.lucene.luke.app.desktop.components.dialog.menubar.CheckIndexDialogFactory; +import org.apache.lucene.luke.app.desktop.components.dialog.menubar.CheckIndexDialogFactoryImpl; +import org.apache.lucene.luke.app.desktop.components.dialog.menubar.OpenIndexDialogFactory; +import org.apache.lucene.luke.app.desktop.components.dialog.menubar.OpenIndexDialogFactoryImpl; +import org.apache.lucene.luke.app.desktop.components.dialog.menubar.OptimizeIndexDialogFactory; +import org.apache.lucene.luke.app.desktop.components.dialog.menubar.OptimizeIndexDialogFactoryImpl; +import org.apache.lucene.luke.app.desktop.components.dialog.search.ExplainDialogFactory; +import org.apache.lucene.luke.app.desktop.components.dialog.search.ExplainDialogFactoryImpl; +import org.apache.lucene.luke.app.desktop.components.fragments.analysis.CustomAnalyzerPanelProvider; +import org.apache.lucene.luke.app.desktop.components.fragments.analysis.PresetAnalyzerPanelProvider; +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.MLTPaneProvider; +import org.apache.lucene.luke.app.desktop.components.fragments.search.QueryParserPaneProvider; +import org.apache.lucene.luke.app.desktop.components.fragments.search.SimilarityPaneProvider; +import org.apache.lucene.luke.app.desktop.components.fragments.search.SortPaneProvider; + +/** Guice configuration */ +public final class DesktopModule extends AbstractModule { + + private static final Injector injector = Guice.createInjector(new DesktopModule()); + + public static Injector getIngector() { + return injector; + } + + @Override + protected void configure() { + // luke core module + install(new LukeModule()); + + // UI components and fragments + bind(ComponentOperatorRegistry.class).toInstance(new ComponentOperatorRegistry()); + bind(TabSwitcherProxy.class).toInstance(new TabSwitcherProxy()); + bind(MessageBroker.class).toInstance(new MessageBroker()); + + bind(JMenuBar.class).toProvider(MenuBarProvider.class); + + bind(JTextArea.class).annotatedWith(Names.named("log_area")).toInstance(new JTextArea()); + + bind(JPanel.class).annotatedWith(Names.named("overview")).toProvider(OverviewPanelProvider.class); + bind(JPanel.class).annotatedWith(Names.named("documents")).toProvider(DocumentsPanelProvider.class); + bind(JPanel.class).annotatedWith(Names.named("search")).toProvider(SearchPanelProvider.class); + bind(JPanel.class).annotatedWith(Names.named("analysis")).toProvider(AnalysisPanelProvider.class); + bind(JPanel.class).annotatedWith(Names.named("commits")).toProvider(CommitsPanelProvider.class); + bind(JPanel.class).annotatedWith(Names.named("logs")).toProvider(LogsPanelProvider.class); + bind(JTabbedPane.class).annotatedWith(Names.named("main")).toProvider(TabbedPaneProvider.class); + + bind(JScrollPane.class).annotatedWith(Names.named("search_qparser")).toProvider(QueryParserPaneProvider.class); + bind(JScrollPane.class).annotatedWith(Names.named("search_analyzer")).toProvider(AnalyzerPaneProvider.class); + bind(JScrollPane.class).annotatedWith(Names.named("search_similarity")).toProvider(SimilarityPaneProvider.class); + bind(JScrollPane.class).annotatedWith(Names.named("search_sort")).toProvider(SortPaneProvider.class); + bind(JScrollPane.class).annotatedWith(Names.named("search_values")).toProvider(FieldValuesPaneProvider.class); + bind(JScrollPane.class).annotatedWith(Names.named("search_mlt")).toProvider(MLTPaneProvider.class); + + bind(JPanel.class).annotatedWith(Names.named("analysis_preset")).toProvider(PresetAnalyzerPanelProvider.class); + bind(JPanel.class).annotatedWith(Names.named("analysis_custom")).toProvider(CustomAnalyzerPanelProvider.class); + + bind(JFrame.class).toProvider(LukeWindowProvider.class); + + bind(OpenIndexDialogFactory.class).to(OpenIndexDialogFactoryImpl.class); + bind(OptimizeIndexDialogFactory.class).to(OptimizeIndexDialogFactoryImpl.class); + bind(CheckIndexDialogFactory.class).to(CheckIndexDialogFactoryImpl.class); + bind(AddDocumentDialogFactory.class).to(AddDocumentDialogFactoryImpl.class); + bind(EditFiltersDialogFactory.class).to(EditFiltersDialogFactoryImpl.class); + bind(EditParamsDialogFactory.class).to(EditParamsDialogFactoryImpl.class); + bind(IndexOptionsDialogFactory.class).to(IndexOptionsDialogFactoryImpl.class); + bind(TermVectorDialogFactory.class).to(TermVectorDialogFactoryImpl.class); + bind(DocValuesDialogFactory.class).to(DocValuesDialogFactoryImpl.class); + bind(StoredValueDialogFactory.class).to(StoredValueDialogFactoryImpl.class); + bind(ExplainDialogFactory.class).to(ExplainDialogFactoryImpl.class); + bind(AnalysisChainDialogFactory.class).to(AnalysisChainDialogFactoryImpl.class); + bind(TokenAttributeDialogFactory.class).to(TokenAttributeDialogFactoryImpl.class); + bind(AboutDialogFactory.class).to(AboutDialogFactoryImpl.class); + bind(HelpDialogFactory.class).to(HelpDialogFactoryImpl.class); + bind(ConfirmDialogFactory.class).to(ConfirmDialogFactoryImpl.class); + } + + private DesktopModule() { + } +} 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 000000000000..40ef26887d2b --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/LukeMain.java @@ -0,0 +1,87 @@ +/* + * 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.JTextArea; +import javax.swing.UIManager; +import java.awt.GraphicsEnvironment; + +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.name.Names; +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.app.desktop.util.TextAreaAppender; + +import static org.apache.lucene.luke.app.desktop.util.ExceptionHandler.handle; + +/** Luke entry class */ +public class LukeMain { + + private static JFrame frame; + + public static JFrame getOwnerFrame() { + return frame; + } + + private static void createAndShowGUI() { + Injector injector = DesktopModule.getIngector(); + + // uncaught error handler + MessageBroker messageBroker = injector.getInstance(MessageBroker.class); + Thread.setDefaultUncaughtExceptionHandler((thread, cause) -> + handle(cause, messageBroker) + ); + + // prepare log4j appender for Logs tab. + JTextArea textArea = injector.getInstance(Key.get(JTextArea.class, Names.named("log_area"))); + TextAreaAppender.setTextArea(textArea); + + + frame = injector.getInstance(JFrame.class); + frame.setLocation(200, 100); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.pack(); + frame.setVisible(true); + + // show open index dialog + OpenIndexDialogFactory openIndexDialogFactory = injector.getInstance(OpenIndexDialogFactory.class); + new DialogOpener<>(openIndexDialogFactory).open(MessageUtils.getLocalizedMessage("openindex.dialog.title"), 600, 420, + (factory) -> { + }); + } + + 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 000000000000..c93a9517f888 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/MessageBroker.java @@ -0,0 +1,59 @@ +/* + * 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 List receivers = new ArrayList<>(); + + 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 */ + 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 000000000000..7ae007f84144 --- /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 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 theme */ + 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/PreferencesImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/PreferencesImpl.java new file mode 100644 index 000000000000..d8bef0005f21 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/PreferencesImpl.java @@ -0,0 +1,145 @@ +/* + * 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.File; +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.store.FSDirectory; +import org.apache.lucene.util.SuppressForbidden; +import org.ini4j.Ini; + +/** 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 Ini ini = new Ini(); + + private final List history = new ArrayList<>(); + + @SuppressForbidden(reason = "ini4j uses java.io.File") + 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 (iniFile().exists()) { + ini.load(iniFile()); + } else { + ini.store(iniFile()); + } + + // load history + Path histFile = historyFile(); + if (Files.exists(histFile)) { + List allHistory = Files.readAllLines(histFile); + history.addAll(allHistory.subList(0, Math.min(MAX_HISTORY, allHistory.size()))); + } + + } + + public List 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.get("settings", "theme", String.class); + 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.get("opener", "readOnly", Boolean.class); + return (readOnly == null) ? false : readOnly; + } + + @Override + public String getDirImpl() { + String dirImpl = ini.get("opener", "dirImpl"); + return (dirImpl == null) ? FSDirectory.class.getName() : dirImpl; + } + + @Override + public boolean isNoReader() { + Boolean noReader = ini.get("opener", "noReader", Boolean.class); + return (noReader == null) ? false : noReader; + } + + @Override + public boolean isUseCompound() { + Boolean useCompound = ini.get("opener", "useCompound", Boolean.class); + return (useCompound == null) ? false : useCompound; + } + + @Override + public boolean isKeepAllCommits() { + Boolean keepAllCommits = ini.get("opener", "keepAllCommits", Boolean.class); + 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()); + } + + @SuppressForbidden(reason = "used for ini4j") + private File iniFile() { + return new File(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 000000000000..cb3a15994c8c --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/AnalysisPanelProvider.java @@ -0,0 +1,440 @@ +/* + * 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.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.name.Named; +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.PresetAnalyzerPanelOperator; +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; + +/** Provider of Analysis panel */ +public final class AnalysisPanelProvider implements Provider, 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 tokens; + + private Analysis analysisModel; + + @Inject + public AnalysisPanelProvider(AnalysisFactory analysisFactory, + ComponentOperatorRegistry operatorRegistry, + AnalysisChainDialogFactory analysisChainDialogFactory, + TokenAttributeDialogFactory tokenAttrDialogFactory, + MessageBroker messageBroker, + @Named("analysis_preset") JPanel preset, + @Named("analysis_custom") JPanel custom) { + this.preset = preset; + this.custom = custom; + + this.operatorRegistry = operatorRegistry; + this.analysisChainDialogFactory = analysisChainDialogFactory; + this.tokenAttrDialogFactory = tokenAttrDialogFactory; + this.messageBroker = messageBroker; + + this.analysisModel = analysisFactory.newInstance(); + analysisModel.createAnalyzerFromClassName(StandardAnalyzer.class.getName()); + + operatorRegistry.register(AnalysisTabOperator.class, this); + + operatorRegistry.get(PresetAnalyzerPanelOperator.class).ifPresent(operator -> { + operator.setPresetAnalyzers(analysisModel.getPresetAnalyzerTypes()); + operator.setSelectedAnalyzer(analysisModel.currentAnalyzer().getClass()); + }); + } + + @Override + 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.setSelected(true); + presetRB.setOpaque(false); + + customRB.setText(MessageUtils.getLocalizedMessage("analysis.radio.custom")); + customRB.setActionCommand(TYPE_CUSTOM); + customRB.addActionListener(listeners::toggleMainPanel); + customRB.setSelected(false); + customRB.setOpaque(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("analysis.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 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); + } + + } + +} + +final class TokensTableModel extends TableModelBase { + + 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 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 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(); + } +} \ No newline at end of file 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 000000000000..07cdbc22b92e --- /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 of 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 000000000000..f23f0465141a --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/CommitsPanelProvider.java @@ -0,0 +1,583 @@ +/* + * 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 com.google.inject.Inject; +import com.google.inject.Provider; +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 Commits panel */ +public final class CommitsPanelProvider implements Provider { + + private final CommitsFactory commitsFactory; + + private final JComboBox 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 segDetailList = new JList<>(); + + private ListenerFunctions listeners = new ListenerFunctions(); + + private Commits commitsModel; + + @Inject + public CommitsPanelProvider(CommitsFactory commitsFactory, + IndexHandler indexHandler, + DirectoryHandler directoryHandler) { + this.commitsFactory = commitsFactory; + + indexHandler.addObserver(new Observer()); + directoryHandler.addObserver(new Observer()); + } + + @Override + 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 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 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 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; + } + +} + +final class FilesTableModel extends TableModelBase { + + 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 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(); + } +} + +final class SegmentsTableModel extends TableModelBase { + + 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 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(); + } +} \ No newline at end of file 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 000000000000..65d58f45f337 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/ComponentOperatorRegistry.java @@ -0,0 +1,44 @@ +/* + * 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 final Map, Object> operators = new HashMap<>(); + + public void register(Class type, T operator) { + if (!operators.containsKey(type)) { + operators.put(type, operator); + } + } + + @SuppressWarnings("unchecked") + public Optional get(Class type) { + return Optional.ofNullable((T) operators.get(type)); + } + + /** Base interface of component operator */ + 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 000000000000..58a23130fd02 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/DocumentsPanelProvider.java @@ -0,0 +1,1123 @@ +/* + * 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.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import com.google.inject.Inject; +import com.google.inject.Provider; +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 Documents panel */ +public final class DocumentsPanelProvider implements Provider, DocumentsTabOperator { + + private final DocumentsFactory 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 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; + + @Inject + public DocumentsPanelProvider(DocumentsFactory documentsFactory, + MessageBroker messageBroker, + IndexHandler indexHandler, + TabSwitcherProxy tabSwitcher, + ComponentOperatorRegistry operatorRegistry, + AddDocumentDialogFactory addDocDialogFactory, + TermVectorDialogFactory tvDialogFactory, + DocValuesDialogFactory dvDialogFactory, + StoredValueDialogFactory valueDialogFactory, + HelpDialogFactory helpDialogFactory) { + this.documentsFactory = documentsFactory; + this.messageBroker = messageBroker; + this.operatorRegistry = operatorRegistry; + this.tabSwitcher = tabSwitcher; + this.addDocDialogFactory = addDocDialogFactory; + this.tvDialogFactory = tvDialogFactory; + this.dvDialogFactory = dvDialogFactory; + this.valueDialogFactory = valueDialogFactory; + this.tableHeaderRenderer = new HelpHeaderRenderer( + "About Flags", "Format: IdfpoNPSB#txxVDtxxxxTx/x", + createFlagsHelpDialog(), helpDialogFactory); + + indexHandler.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 list = new JList<>(values); + return new JScrollPane(list); + } + + @Override + 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(); + + String firstTermText = documentsModel.firstTerm(fieldName).map(Term::text).orElse(""); + termTF.setText(firstTermText); + selectedTermTF.setText(firstTermText); + if (selectedTermTF.getText().length() > 0) { + 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(); + + String nextTermText = documentsModel.nextTerm().map(Term::text).orElse(""); + termTF.setText(nextTermText); + selectedTermTF.setText(nextTermText); + if (selectedTermTF.getText().length() > 0) { + 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(); + + String nextTermText = documentsModel.seekTerm(termText).map(Term::text).orElse(""); + termTF.setText(nextTermText); + selectedTermTF.setText(nextTermText); + if (selectedTermTF.getText().length() > 0) { + 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 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 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 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", 400, 300, + (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 = 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 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()); + } + } + +} + +final class PosTableModel extends TableModelBase { + + 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 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(); + } +} + +final class DocumentsTableModel extends TableModelBase { + + 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 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 000000000000..8216a159bd4b --- /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 of 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 000000000000..85866fb2ce9a --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LogsPanelProvider.java @@ -0,0 +1,67 @@ +/* + * 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 java.nio.file.FileSystems; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.name.Named; +import org.apache.lucene.luke.app.desktop.util.MessageUtils; + +/** Provider of Logs panel */ +public final class LogsPanelProvider implements Provider { + + private static final String LOG_FILE = System.getProperty("user.home") + + FileSystems.getDefault().getSeparator() + ".luke.d" + + FileSystems.getDefault().getSeparator() + "luke.log"; + + private final JTextArea logTextArea; + + @Inject + public LogsPanelProvider(@Named("log_area") JTextArea logTextArea) { + this.logTextArea = logTextArea; + } + + @Override + 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(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 000000000000..2eb3c08dd50e --- /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 of the top level 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 000000000000..526e18431f2a --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LukeWindowProvider.java @@ -0,0 +1,253 @@ +/* + * 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.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 com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.name.Named; +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.util.FontUtils; +import org.apache.lucene.luke.app.desktop.util.ImageUtils; +import org.apache.lucene.luke.app.desktop.util.MessageUtils; +import org.apache.lucene.util.Version; + +/** Provider of the top level window */ +public final class LukeWindowProvider implements Provider, 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(); + + @Inject + public LukeWindowProvider(Preferences prefs, + ComponentOperatorRegistry operatorRegistry, + JMenuBar menuBar, + @Named("main") JTabbedPane tabbedPane, + DirectoryHandler directoryHandler, + IndexHandler indexHandler, + MessageBroker messageBroker, + TabSwitcherProxy tabSwitcher) { + this.prefs = prefs; + this.menuBar = menuBar; + this.tabbedPane = tabbedPane; + this.messageBroker = messageBroker; + this.tabSwitcher = tabSwitcher; + + operatorRegistry.register(LukeWindowOperator.class, this); + Observer observer = new Observer(); + directoryHandler.addObserver(observer); + indexHandler.addObserver(observer); + + messageBroker.registerReceiver(new MessageReceiverImpl()); + } + + @Override + 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("/img/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 000000000000..3dd424aea003 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/MenuBarProvider.java @@ -0,0 +1,295 @@ +/* + * 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 com.google.inject.Inject; +import com.google.inject.Provider; +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.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.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 menu bar */ +public final class MenuBarProvider implements Provider { + + private final Preferences prefs; + + private final ComponentOperatorRegistry operatorRegistry; + + private final DirectoryHandler directoryHandler; + + private final IndexHandler indexHandler; + + private final OpenIndexDialogFactory openIndexDialogFactory; + + 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 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(); + + @Inject + public MenuBarProvider(Preferences prefs, ComponentOperatorRegistry operatorRegistry, + DirectoryHandler directoryHandler, IndexHandler indexHandler, + OpenIndexDialogFactory openIndexDialogFactory, + OptimizeIndexDialogFactory optimizeIndexDialogFactory, + CheckIndexDialogFactory checkIndexDialogFactory, + AboutDialogFactory aboutDialogFactory) { + this.prefs = prefs; + this.operatorRegistry = operatorRegistry; + this.directoryHandler = directoryHandler; + this.indexHandler = indexHandler; + this.openIndexDialogFactory = openIndexDialogFactory; + this.optimizeIndexDialogFactory = optimizeIndexDialogFactory; + this.checkIndexDialogFactory = checkIndexDialogFactory; + this.aboutDialogFactory = aboutDialogFactory; + + 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); + + 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 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 000000000000..30f37d9384f8 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/OverviewPanelProvider.java @@ -0,0 +1,653 @@ +/* + * 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 com.google.inject.Inject; +import com.google.inject.Provider; +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 Overview panel */ +public final class OverviewPanelProvider implements Provider { + + 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; + + 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; + + @Inject + public OverviewPanelProvider( + OverviewFactory overviewFactory, + MessageBroker messageBroker, + ComponentOperatorRegistry operatorRegistry, + IndexHandler indexHandler, + TabSwitcherProxy tabSwitcher) { + this.overviewFactory = overviewFactory; + this.messageBroker = messageBroker; + this.operatorRegistry = operatorRegistry; + this.tabSwitcher = tabSwitcher; + + indexHandler.addObserver(new Observer()); + } + + @Override + 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 = 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 row = termCountsTable.getSelectedRow(); + 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 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()); + } + + } + + +} + +final class TermCountsTableModel extends TableModelBase { + + 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 termCounts) { + super(termCounts.size()); + int i = 0; + for (Map.Entry 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(); + } +} + +final class TopTermsTableModel extends TableModelBase { + + 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, 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(); + } +} \ No newline at end of file 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 000000000000..5562d65ffe3c --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/SearchPanelProvider.java @@ -0,0 +1,833 @@ +/* + * 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.awt.event.MouseListener; +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 com.google.common.base.Strings; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.name.Named; +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.FieldValuesTabOperator; +import org.apache.lucene.luke.app.desktop.components.fragments.search.MLTTabOperator; +import org.apache.lucene.luke.app.desktop.components.fragments.search.QueryParserTabOperator; +import org.apache.lucene.luke.app.desktop.components.fragments.search.SimilarityTabOperator; +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.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; + +/** Provider of Search panel */ +public final class SearchPanelProvider implements Provider, 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 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; + + @Inject + public SearchPanelProvider(SearchFactory searchFactory, + IndexToolsFactory toolsFactory, + IndexHandler indexHandler, + MessageBroker messageBroker, + TabSwitcherProxy tabSwitcher, + ComponentOperatorRegistry operatorRegistry, + ConfirmDialogFactory confirmDialogFactory, + ExplainDialogFactory explainDialogProvider, + @Named("search_qparser") JScrollPane qparser, + @Named("search_analyzer") JScrollPane analyzer, + @Named("search_similarity") JScrollPane similarity, + @Named("search_sort") JScrollPane sort, + @Named("search_values") JScrollPane values, + @Named("search_mlt") JScrollPane mlt) { + this.searchFactory = searchFactory; + this.toolsFactory = toolsFactory; + this.indexHandler = indexHandler; + this.messageBroker = messageBroker; + this.tabSwitcher = tabSwitcher; + this.operatorRegistry = operatorRegistry; + this.confirmDialogFactory = confirmDialogFactory; + this.explainDialogProvider = explainDialogProvider; + this.qparser = qparser; + this.analyzer = analyzer; + this.similarity = similarity; + this.sort = sort; + this.values = values; + this.mlt = mlt; + + indexHandler.addObserver(new Observer()); + operatorRegistry.register(SearchTabOperator.class, this); + } + + @Override + 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.setOpaque(false); + termQueryCB.addActionListener(listeners::toggleTermQuery); + 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.0; + c.insets = new Insets(5, 0, 5, 0); + panel.add(searchBtn, 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()); + docNo.setOpaque(false); + JLabel docNoLabel = new JLabel("with doc #"); + docNo.add(docNoLabel); + mltDocFTF.setColumns(3); + 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); + + MouseListener mouseListener = new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + listeners.showContextMenuInResultsTable(e); + } + }; + 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); + mltBtn.setEnabled(false); + mltDocFTF.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); + mltBtn.setEnabled(true); + mltDocFTF.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 (Strings.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 fieldsToLoad = operatorRegistry.get(FieldValuesTabOperator.class) + .map(FieldValuesTabOperator::getFieldsToLoad) + .orElse(Collections.emptySet()); + SearchResults results = searchModel.search(query, simConfig, sort, fieldsToLoad, DEFAULT_PAGE_SIZE); + + 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 fieldsToLoad = operatorRegistry.get(FieldValuesTabOperator.class) + .map(FieldValuesTabOperator::getFieldsToLoad) + .orElse(Collections.emptySet()); + SearchResults results = searchModel.search(query, new SimilarityConfig.Builder().build(), fieldsToLoad, DEFAULT_PAGE_SIZE); + + 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 = Strings.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() > 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() > 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()); + } + + 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()); + } + + } + + 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; + } + } + +} + +final class SearchResultsTableModel extends TableModelBase { + + 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 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(); + } +} \ No newline at end of file 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 000000000000..8bd52c5ae581 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/SearchTabOperator.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; + +/** Operator of the Search tab */ +public interface SearchTabOperator extends ComponentOperatorRegistry.ComponentOperator { + void searchByTerm(String field, String term); + + void mltSearch(int docNum); +} 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 000000000000..8f8602cfdb3f --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TabSwitcherProxy.java @@ -0,0 +1,46 @@ +/* + * 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.ArrayList; +import java.util.List; + +/** An utility class for switching tabs. */ +public class TabSwitcherProxy { + + private final List switcherHolder = new ArrayList<>(); + + public void set(TabSwitcher switcher) { + if (switcherHolder.isEmpty()) { + switcherHolder.add(switcher); + } + } + + public void switchTab(TabbedPaneProvider.Tab tab) { + if (switcherHolder.get(0) == null) { + throw new IllegalStateException(); + } + switcherHolder.get(0).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 000000000000..e482e7d5724d --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TabbedPaneProvider.java @@ -0,0 +1,148 @@ +/* + * 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 com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.name.Named; +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 Provider, 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; + + @Inject + public TabbedPaneProvider(@Named("overview") JPanel overviewPanel, + @Named("documents") JPanel documentsPanel, + @Named("search") JPanel searchPanel, + @Named("analysis") JPanel analysisPanel, + @Named("commits") JPanel commitsPanel, + @Named("logs") JPanel logsPanel, + IndexHandler indexHandler, + DirectoryHandler directoryHandler, + TabSwitcherProxy tabSwitcher, + MessageBroker messageBroker) { + this.overviewPanel = overviewPanel; + this.documentsPanel = documentsPanel; + this.searchPanel = searchPanel; + this.analysisPanel = analysisPanel; + this.commitsPanel = commitsPanel; + this.logsPanel = logsPanel; + + this.messageBroker = messageBroker; + + tabSwitcher.set(this); + + Observer observer = new Observer(); + indexHandler.addObserver(observer); + directoryHandler.addObserver(observer); + } + + @Override + 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); + } + } + + /** Tab */ + 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 000000000000..63cdbb10700a --- /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 000000000000..f8ef21a41efb --- /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 extends AbstractTableModel { + + private final Map 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 000000000000..caac4bd972f5 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/ConfirmDialogFactory.java @@ -0,0 +1,28 @@ +/* + * 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 org.apache.lucene.luke.app.desktop.util.DialogOpener; +import org.apache.lucene.luke.app.desktop.util.lang.Callable; + +/** Factory of confirm dialog */ +public interface ConfirmDialogFactory extends DialogOpener.DialogFactory { + void setMessage(String message); + + void setCallback(Callable callback); +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/ConfirmDialogFactoryImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/ConfirmDialogFactoryImpl.java new file mode 100644 index 000000000000..759061a8c605 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/ConfirmDialogFactoryImpl.java @@ -0,0 +1,111 @@ +/* + * 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 com.google.inject.Inject; +import org.apache.lucene.luke.app.desktop.Preferences; +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; + +/** Default implementation of {@link ConfirmDialogFactory} */ +public final class ConfirmDialogFactoryImpl implements ConfirmDialogFactory { + + private final Preferences prefs; + + private JDialog dialog; + + private String message; + + private Callable callback; + + @Inject + public ConfirmDialogFactoryImpl(Preferences prefs) { + this.prefs = prefs; + } + + @Override + public void setMessage(String message) { + this.message = message; + } + + @Override + 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 000000000000..dd1aaf9b4ca5 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/HelpDialogFactory.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.dialog; + +import javax.swing.JComponent; + +import org.apache.lucene.luke.app.desktop.util.DialogOpener; + +/** Factory of help dialog */ +public interface HelpDialogFactory extends DialogOpener.DialogFactory { + void setDesc(String desc); + + void setContent(JComponent helpContent); +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/HelpDialogFactoryImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/HelpDialogFactoryImpl.java new file mode 100644 index 000000000000..f5565ac8d4fe --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/HelpDialogFactoryImpl.java @@ -0,0 +1,98 @@ +/* + * 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 com.google.inject.Inject; +import org.apache.lucene.luke.app.desktop.Preferences; +import org.apache.lucene.luke.app.desktop.util.MessageUtils; + +/** Default implementation of {@link HelpDialogFactory} */ +public final class HelpDialogFactoryImpl implements HelpDialogFactory { + + private final Preferences prefs; + + private JDialog dialog; + + private String desc; + + private JComponent helpContent; + + @Inject + public HelpDialogFactoryImpl(Preferences prefs) { + this.prefs = prefs; + } + + @Override + public void setDesc(String desc) { + this.desc = desc; + } + + @Override + 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 000000000000..d655e36e5eab --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/AnalysisChainDialogFactory.java @@ -0,0 +1,26 @@ +/* + * 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 org.apache.lucene.analysis.custom.CustomAnalyzer; +import org.apache.lucene.luke.app.desktop.util.DialogOpener; + +/** Factory of analysis chain dialog */ +public interface AnalysisChainDialogFactory extends DialogOpener.DialogFactory { + void setAnalyzer(CustomAnalyzer analyzer); +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/AnalysisChainDialogFactoryImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/AnalysisChainDialogFactoryImpl.java new file mode 100644 index 000000000000..6061f7a0af24 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/AnalysisChainDialogFactoryImpl.java @@ -0,0 +1,148 @@ +/* + * 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 com.google.inject.Inject; +import org.apache.lucene.analysis.custom.CustomAnalyzer; +import org.apache.lucene.luke.app.desktop.Preferences; +import org.apache.lucene.luke.app.desktop.util.MessageUtils; + +/** Default implementation of {@link AnalysisChainDialogFactory} */ +public class AnalysisChainDialogFactoryImpl implements AnalysisChainDialogFactory { + + private final Preferences prefs; + + private JDialog dialog; + + private CustomAnalyzer analyzer; + + @Inject + public AnalysisChainDialogFactoryImpl(Preferences prefs) { + this.prefs = prefs; + } + + 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 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 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 000000000000..58314201519d --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditFiltersDialogFactory.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.dialog.analysis; + +import java.util.List; + +import org.apache.lucene.luke.app.desktop.util.DialogOpener; +import org.apache.lucene.luke.app.desktop.util.lang.Callable; + +/** Factory of edit filters dialog */ +public interface EditFiltersDialogFactory extends DialogOpener.DialogFactory { + void setSelectedFilters(List selectedFilters); + + void setCallback(Callable callback); + + void setMode(EditFiltersMode mode); +} + diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditFiltersDialogFactoryImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditFiltersDialogFactoryImpl.java new file mode 100644 index 000000000000..6481e97ceea5 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditFiltersDialogFactoryImpl.java @@ -0,0 +1,300 @@ +/* + * 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.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import com.google.inject.Inject; +import org.apache.lucene.luke.app.desktop.Preferences; +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; + +/** Default implementation of {@link EditFiltersDialogFactory} */ +public final class EditFiltersDialogFactoryImpl implements EditFiltersDialogFactory { + + 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 selectedFilters; + + private Callable callback; + + private EditFiltersMode mode; + + @Inject + public EditFiltersDialogFactoryImpl(Preferences prefs, + ComponentOperatorRegistry operatorRegistry, + EditParamsDialogFactory editParamsDialogFactory) { + this.prefs = prefs; + this.operatorRegistry = operatorRegistry; + this.editParamsDialogFactory = editParamsDialogFactory; + } + + @Override + public void setSelectedFilters(List selectedFilters) { + this.selectedFilters = selectedFilters; + } + + @Override + public void setCallback(Callable callback) { + this.callback = callback; + } + + @Override + 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 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 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 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); + } + } +} + +final class FiltersTableModel extends TableModelBase { + + 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 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(); + } +} + +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 000000000000..d5edd8b505e0 --- /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 000000000000..abf1fc127174 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditParamsDialogFactory.java @@ -0,0 +1,36 @@ +/* + * 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 java.util.Map; + +import org.apache.lucene.luke.app.desktop.util.DialogOpener; +import org.apache.lucene.luke.app.desktop.util.lang.Callable; + +/** Factory of edit parameters dialog */ +public interface EditParamsDialogFactory extends DialogOpener.DialogFactory { + void setMode(EditParamsMode mode); + + void setTarget(String target); + + void setTargetIndex(int targetIndex); + + void setParams(Map params); + + void setCallback(Callable callback); +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditParamsDialogFactoryImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditParamsDialogFactoryImpl.java new file mode 100644 index 000000000000..542e6f34cd62 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditParamsDialogFactoryImpl.java @@ -0,0 +1,248 @@ +/* + * 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.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import com.google.inject.Inject; +import org.apache.lucene.luke.app.desktop.Preferences; +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.MessageUtils; +import org.apache.lucene.luke.app.desktop.util.TableUtils; +import org.apache.lucene.luke.app.desktop.util.lang.Callable; + +/** Default implementation of {@link EditParamsDialogFactory} */ +public final class EditParamsDialogFactoryImpl implements EditParamsDialogFactory { + + 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 params = new HashMap<>(); + + private Callable callback; + + @Inject + public EditParamsDialogFactoryImpl(Preferences prefs, ComponentOperatorRegistry operatorRegistry) { + this.prefs = prefs; + this.operatorRegistry = operatorRegistry; + } + + @Override + public void setMode(EditParamsMode mode) { + this.mode = mode; + } + + @Override + public void setTarget(String target) { + this.target = target; + } + + @Override + public void setTargetIndex(int targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public void setParams(Map params) { + this.params.putAll(params); + } + + @Override + 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 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 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; + } + }); + } + +} + +final class ParamsTableModel extends TableModelBase { + + 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 params) { + super(PARAM_SIZE); + List 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 000000000000..5771403d4c63 --- /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 000000000000..511d72edc1e8 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/TokenAttributeDialogFactory.java @@ -0,0 +1,30 @@ +/* + * 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 java.util.List; + +import org.apache.lucene.luke.app.desktop.util.DialogOpener; +import org.apache.lucene.luke.models.analysis.Analysis; + +/** Factory of token attribute dialog */ +public interface TokenAttributeDialogFactory extends DialogOpener.DialogFactory { + void setTerm(String term); + + void setAttributes(List attributes); +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/TokenAttributeDialogFactoryImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/TokenAttributeDialogFactoryImpl.java new file mode 100644 index 000000000000..c2ef7e367b6e --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/TokenAttributeDialogFactoryImpl.java @@ -0,0 +1,188 @@ +/* + * 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.util.List; +import java.util.stream.Collectors; + +import com.google.inject.Inject; +import org.apache.lucene.luke.app.desktop.Preferences; +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.MessageUtils; +import org.apache.lucene.luke.app.desktop.util.TableUtils; +import org.apache.lucene.luke.models.analysis.Analysis; + +/** Default implementation of {@link TokenAttributeDialogFactory} */ +public final class TokenAttributeDialogFactoryImpl implements TokenAttributeDialogFactory { + + private final Preferences prefs; + + private final JTable attributesTable = new JTable(); + + private JDialog dialog; + + private String term; + + private List attributes; + + @Inject + public TokenAttributeDialogFactoryImpl(Preferences prefs) { + this.prefs = prefs; + } + + @Override + public void setTerm(String term) { + this.term = term; + } + + @Override + public void setAttributes(List 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 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; + } + +} + +final class AttributeTableModel extends TableModelBase { + + 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 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(); + } +} + +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 000000000000..bd3419bd66fa --- /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 000000000000..47bb60d8d96b --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/AddDocumentDialogFactory.java @@ -0,0 +1,24 @@ +/* + * 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.luke.app.desktop.util.DialogOpener; + +/** Factory of add document dialog */ +public interface AddDocumentDialogFactory extends DialogOpener.DialogFactory { +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/AddDocumentDialogFactoryImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/AddDocumentDialogFactoryImpl.java new file mode 100644 index 000000000000..7cc24dd7fc13 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/AddDocumentDialogFactoryImpl.java @@ -0,0 +1,587 @@ +/* + * 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.lang.invoke.MethodHandles; +import java.lang.reflect.Constructor; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import com.google.common.base.Strings; +import com.google.inject.Inject; +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.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.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.util.BytesRef; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Default implementation of {@link AddDocumentDialogFactory} */ +public final class AddDocumentDialogFactoryImpl implements AddDocumentDialogFactory, AddDocumentDialogOperator { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private static final int ROW_COUNT = 50; + + private final Preferences prefs; + + private final IndexHandler indexHandler; + + private final IndexToolsFactory toolsFactory; + + 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 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; + + @Inject + public AddDocumentDialogFactoryImpl(Preferences prefs, + IndexOptionsDialogFactory indexOptionsDialogFactory, HelpDialogFactory helpDialogFactory, + IndexHandler indexHandler, IndexToolsFactory toolsFactory, + TabSwitcherProxy tabSwitcher, + ComponentOperatorRegistry operatorRegistry) { + this.prefs = prefs; + this.indexHandler = indexHandler; + this.toolsFactory = toolsFactory; + this.tabSwitcher = tabSwitcher; + this.operatorRegistry = operatorRegistry; + this.indexOptionsDialogFactory = indexOptionsDialogFactory; + this.helpDialogFactory = helpDialogFactory; + 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); + 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> 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 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") + private final Class[] 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 validFields = newFieldList.stream() + .filter(nf -> !nf.isDeleted()) + .filter(nf -> !Strings.isNullOrEmpty(nf.getName())) + .filter(nf -> !Strings.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 { + if (nf.getType().equals(TextField.class) || nf.getType().equals(StringField.class)) { + Field.Store store = nf.isStored() ? Field.Store.YES : Field.Store.NO; + Constructor 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)) { + Constructor 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)) { + Constructor 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)) { + Constructor 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)) { + Constructor 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)) { + Constructor 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)) { + Constructor 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)) { + Constructor constr = nf.getType().getConstructor(String.class, String.class); + return constr.newInstance(nf.getName(), nf.getValue()); + } else if (nf.getType().equals(Field.class)) { + Constructor 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; + } + } + +} + +final class FieldsTableModel extends TableModelBase { + + 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 newFieldList; + + FieldsTableModel(List 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(); + } + + @Override + @SuppressWarnings("unchecked") + 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) value); + selectedField.resetFieldType((Class) value); + selectedField.setStored(selectedField.getFieldType().stored()); + } else if (columnIndex == Column.VALUE.getIndex()) { + selectedField.setValue((String) value); + } + } + + @Override + protected Column[] columnInfos() { + return Column.values(); + } +} + +final class TypeCellRenderer implements TableCellRenderer { + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + String simpleName = ((Class) value).getSimpleName(); + return new JLabel(simpleName); + } +} + +final class OptionsCellRenderer implements TableCellRenderer { + + private JDialog dialog; + + private final IndexOptionsDialogFactory indexOptionsDialogFactory; + + private final List newFieldList; + + private final JPanel panel = new JPanel(); + + private JTable table; + + public OptionsCellRenderer(JDialog dialog, IndexOptionsDialogFactory indexOptionsDialogFactory, List 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; + } + +} \ No newline at end of file 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 000000000000..2c29d6fd5dbb --- /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 000000000000..b47b21bad87e --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/DocValuesDialogFactory.java @@ -0,0 +1,26 @@ +/* + * 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.luke.app.desktop.util.DialogOpener; +import org.apache.lucene.luke.models.documents.DocValues; + +/** Factory of doc values dialog */ +public interface DocValuesDialogFactory extends DialogOpener.DialogFactory { + void setValue(String field, DocValues docValues); +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/DocValuesDialogFactoryImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/DocValuesDialogFactoryImpl.java new file mode 100644 index 000000000000..b5695e2db994 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/DocValuesDialogFactoryImpl.java @@ -0,0 +1,286 @@ +/* + * 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.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import com.google.inject.Inject; +import org.apache.lucene.luke.app.desktop.Preferences; +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; + +/** Default implementation of {@link DocValuesDialogFactory} */ +public final class DocValuesDialogFactoryImpl implements DocValuesDialogFactory { + + private final Preferences prefs; + + private final JComboBox decodersCombo = new JComboBox<>(); + + private final JList valueList = new JList<>(); + + private final ListenerFunctions listeners = new ListenerFunctions(); + + private JDialog dialog; + + private String field; + + private DocValues docValues; + + @Inject + public DocValuesDialogFactoryImpl(Preferences prefs) { + this.prefs = prefs; + } + + @Override + public void setValue(String field, DocValues docValues) { + this.field = field; + this.docValues = docValues; + + DefaultListModel 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 valueList() { + valueList.setVisibleRowCount(5); + valueList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + valueList.setLayoutOrientation(JList.VERTICAL); + + DefaultListModel 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 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 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 getAllVlues() { + List 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) { + DocValuesDialogFactoryImpl.this.selectDecoder(); + } + + void copyValues(ActionEvent e) { + DocValuesDialogFactoryImpl.this.copyValues(); + } + } + + + 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 000000000000..86c4f9894ebf --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/IndexOptionsDialogFactory.java @@ -0,0 +1,26 @@ +/* + * 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.luke.app.desktop.dto.documents.NewField; +import org.apache.lucene.luke.app.desktop.util.DialogOpener; + +/** Factory of index options dialog */ +public interface IndexOptionsDialogFactory extends DialogOpener.DialogFactory { + void setNewField(NewField nf); +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/IndexOptionsDialogFactoryImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/IndexOptionsDialogFactoryImpl.java new file mode 100644 index 000000000000..693f75d388a6 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/IndexOptionsDialogFactoryImpl.java @@ -0,0 +1,298 @@ +/* + * 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.util.Arrays; + +import com.google.inject.Inject; +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.dto.documents.NewField; +import org.apache.lucene.luke.app.desktop.util.MessageUtils; + +/** Default implementation of {@link IndexOptionsDialogFactory} */ +public final class IndexOptionsDialogFactoryImpl implements IndexOptionsDialogFactory { + + 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 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 dvTypeCombo = new JComboBox<>(availableDocValuesType()); + + private final JTextField dimCountTF = new JTextField(); + + private final JTextField dimNumBytesTF = new JTextField(); + + private JDialog dialog; + + private NewField nf; + + @Inject + public IndexOptionsDialogFactoryImpl(Preferences prefs) { + this.prefs = prefs; + 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 000000000000..ef391664160c --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/StoredValueDialogFactory.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.luke.app.desktop.util.DialogOpener; + +/** Factory of stored values dialog */ +public interface StoredValueDialogFactory extends DialogOpener.DialogFactory { + void setField(String field); + + void setValue(String value); +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/StoredValueDialogFactoryImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/StoredValueDialogFactoryImpl.java new file mode 100644 index 000000000000..cf55f06b78f6 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/StoredValueDialogFactoryImpl.java @@ -0,0 +1,124 @@ +/* + * 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.util.Objects; + +import com.google.inject.Inject; +import org.apache.lucene.luke.app.desktop.Preferences; +import org.apache.lucene.luke.app.desktop.util.FontUtils; +import org.apache.lucene.luke.app.desktop.util.MessageUtils; + +/** Default implementation of {@link StoredValueDialogFactory} */ +public final class StoredValueDialogFactoryImpl implements StoredValueDialogFactory { + + private final Preferences prefs; + + private JDialog dialog; + + private String field; + + private String value; + + @Override + public void setField(String field) { + this.field = field; + } + + @Override + public void setValue(String value) { + this.value = value; + } + + @Inject + public StoredValueDialogFactoryImpl(Preferences prefs) { + this.prefs = prefs; + } + + @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 000000000000..eb479735977e --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/TermVectorDialogFactory.java @@ -0,0 +1,30 @@ +/* + * 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 java.util.List; + +import org.apache.lucene.luke.app.desktop.util.DialogOpener; +import org.apache.lucene.luke.models.documents.TermVectorEntry; + +/** Factory of term vector dialog */ +public interface TermVectorDialogFactory extends DialogOpener.DialogFactory { + void setField(String field); + + void setTvEntries(List tvEntries); +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/TermVectorDialogFactoryImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/TermVectorDialogFactoryImpl.java new file mode 100644 index 000000000000..90c4b6f6dcf6 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/TermVectorDialogFactoryImpl.java @@ -0,0 +1,182 @@ +/* + * 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.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import com.google.inject.Inject; +import org.apache.lucene.luke.app.desktop.Preferences; +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.MessageUtils; +import org.apache.lucene.luke.app.desktop.util.TableUtils; +import org.apache.lucene.luke.models.documents.TermVectorEntry; + +/** Default implementation of {@link TermVectorDialogFactory} */ +public final class TermVectorDialogFactoryImpl implements TermVectorDialogFactory { + + private final Preferences prefs; + + private JDialog dialog; + + private String field; + + private List tvEntries; + + @Inject + public TermVectorDialogFactoryImpl(Preferences prefs) { + this.prefs = prefs; + } + + @Override + public void setField(String field) { + this.field = field; + } + + @Override + public void setTvEntries(List 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; + } + +} + +final class TermVectorTableModel extends TableModelBase { + + 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 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(); + } +} \ No newline at end of file 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 000000000000..9c641f994697 --- /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 000000000000..3fffdc6c184e --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/AboutDialogFactory.java @@ -0,0 +1,24 @@ +/* + * 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 org.apache.lucene.luke.app.desktop.util.DialogOpener; + +/** Factory of about dialog */ +public interface AboutDialogFactory extends DialogOpener.DialogFactory { +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/AboutDialogFactoryImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/AboutDialogFactoryImpl.java new file mode 100644 index 000000000000..3fe1e3e0671b --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/AboutDialogFactoryImpl.java @@ -0,0 +1,180 @@ +/* + * 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.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 com.google.inject.Inject; +import org.apache.lucene.luke.app.desktop.Preferences; +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; + +/** Default implementation of {@link AboutDialogFactory} */ +public final class AboutDialogFactoryImpl implements AboutDialogFactory { + + private final Preferences prefs; + + private JDialog dialog; + + @Inject + public AboutDialogFactoryImpl(Preferences prefs) { + this.prefs = prefs; + } + + @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("/img/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("/img/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(5, 5, 5, 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)); + //scrollPane.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 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 LICENSE_NOTICE = + "

[License]

" + + "

Luke is distributed under Apache License Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) " + + "and includes The Elegant Icon Font (https://www.elegantthemes.com/blog/resources/elegant-icon-font) " + + "licensed under MIT (https://opensource.org/licenses/MIT)

" + + "

[Brief history]

" + + "
    " + + "
  • The original author is Andrzej Bialecki
  • " + + "
  • The project has been mavenized by Neil Ireson
  • " + + "
  • The project has been ported to Lucene trunk (marked as 5.0 at the time) by Dmitry Kan\n
  • " + + "
  • The project has been back-ported to Lucene 4.3 by sonarname
  • " + + "
  • There are updates to the (non-mavenized) project done by tarzanek
  • " + + "
  • The UI and core components has been re-implemented on top of Swing by Tomoko Uchida
  • " + + "
"; + + + private static final HyperlinkListener hyperlinkListener = e -> { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) + if (Desktop.isDesktopSupported()) { + try { + Desktop.getDesktop().browse(e.getURL().toURI()); + } catch (IOException | URISyntaxException ex) { + throw new LukeException(ex.getMessage(), ex); + } + } + }; + + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/CheckIndexDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/CheckIndexDialogFactory.java new file mode 100644 index 000000000000..007d5f257eab --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/CheckIndexDialogFactory.java @@ -0,0 +1,24 @@ +/* + * 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 org.apache.lucene.luke.app.desktop.util.DialogOpener; + +/** Factory of check index dialog */ +public interface CheckIndexDialogFactory extends DialogOpener.DialogFactory { +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/CheckIndexDialogFactoryImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/CheckIndexDialogFactoryImpl.java new file mode 100644 index 000000000000..3543ae5824b4 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/CheckIndexDialogFactoryImpl.java @@ -0,0 +1,377 @@ +/* + * 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.annotation.Nullable; +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.JTextArea; +import javax.swing.SwingWorker; +import java.awt.BorderLayout; +import java.awt.Dialog; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.GridLayout; +import java.awt.Insets; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.io.ByteArrayOutputStream; +import java.io.UnsupportedEncodingException; +import java.lang.invoke.MethodHandles; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import com.google.inject.Inject; +import org.apache.lucene.index.CheckIndex; +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.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.StyleConstants; +import org.apache.lucene.luke.app.desktop.util.TextAreaPrintStream; +import org.apache.lucene.luke.models.tools.IndexTools; +import org.apache.lucene.luke.models.tools.IndexToolsFactory; +import org.apache.lucene.util.NamedThreadFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Default implementation of {@link CheckIndexDialogFactory} */ +public final class CheckIndexDialogFactoryImpl implements CheckIndexDialogFactory { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private final Preferences prefs; + + private final IndexToolsFactory indexToolsFactory; + + private final IndexHandler indexHandler; + + private final JLabel resultLbl = new JLabel(); + + private final JLabel statusLbl = new JLabel(); + + private final JLabel indicatorLbl = new JLabel(); + + private final JButton repairBtn = new JButton(); + + private final JTextArea logArea = new JTextArea(); + + private JDialog dialog; + + private LukeState lukeState; + + private CheckIndex.Status status; + + private IndexTools toolsModel; + + private final ListenerFunctions listeners = new ListenerFunctions(); + + @Inject + public CheckIndexDialogFactoryImpl(Preferences prefs, IndexToolsFactory indexToolsFactory, IndexHandler indexHandler, DirectoryHandler directoryHandler) { + this.prefs = prefs; + this.indexToolsFactory = indexToolsFactory; + this.indexHandler = indexHandler; + + indexHandler.addObserver(new Observer()); + directoryHandler.addObserver(new Observer()); + + initialize(); + } + + private void initialize() { + repairBtn.setText(FontUtils.elegantIconHtml("", MessageUtils.getLocalizedMessage("checkidx.button.fix"))); + repairBtn.setFont(StyleConstants.FONT_BUTTON_LARGE); + repairBtn.setMargin(new Insets(3, 3, 3, 3)); + repairBtn.setEnabled(false); + repairBtn.addActionListener(listeners::repairIndex); + + indicatorLbl.setIcon(ImageUtils.createImageIcon("/img/indicator.gif", 20, 20)); + + logArea.setEditable(false); + } + + + @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(controller()); + panel.add(new JSeparator(JSeparator.HORIZONTAL)); + panel.add(logs()); + + return panel; + } + + private JPanel controller() { + JPanel panel = new JPanel(new GridLayout(3, 1)); + panel.setOpaque(false); + + JPanel idxPath = new JPanel(new FlowLayout(FlowLayout.LEADING)); + idxPath.setOpaque(false); + idxPath.add(new JLabel(MessageUtils.getLocalizedMessage("checkidx.label.index_path"))); + JLabel idxPathLbl = new JLabel(lukeState.getIndexPath()); + idxPathLbl.setToolTipText(lukeState.getIndexPath()); + idxPath.add(idxPathLbl); + panel.add(idxPath); + + JPanel results = new JPanel(new GridLayout(2, 1)); + results.setOpaque(false); + results.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0)); + results.add(new JLabel(MessageUtils.getLocalizedMessage("checkidx.label.results"))); + results.add(resultLbl); + panel.add(results); + + JPanel execButtons = new JPanel(new FlowLayout(FlowLayout.TRAILING)); + execButtons.setOpaque(false); + JButton checkBtn = new JButton(FontUtils.elegantIconHtml("", MessageUtils.getLocalizedMessage("checkidx.button.check"))); + checkBtn.setFont(StyleConstants.FONT_BUTTON_LARGE); + checkBtn.setMargin(new Insets(3, 0, 3, 0)); + checkBtn.addActionListener(listeners::checkIndex); + execButtons.add(checkBtn); + + JButton closeBtn = new JButton(MessageUtils.getLocalizedMessage("button.close")); + closeBtn.setFont(StyleConstants.FONT_BUTTON_LARGE); + closeBtn.setMargin(new Insets(3, 0, 3, 0)); + closeBtn.addActionListener(e -> dialog.dispose()); + execButtons.add(closeBtn); + panel.add(execButtons); + + return panel; + } + + private JPanel logs() { + JPanel panel = new JPanel(new BorderLayout()); + panel.setOpaque(false); + + JPanel header = new JPanel(); + header.setOpaque(false); + header.setLayout(new BoxLayout(header, BoxLayout.PAGE_AXIS)); + + JPanel repair = new JPanel(new FlowLayout(FlowLayout.LEADING)); + repair.setOpaque(false); + repair.add(repairBtn); + + JTextArea warnArea = new JTextArea(MessageUtils.getLocalizedMessage("checkidx.label.warn"), 3, 30); + warnArea.setLineWrap(true); + warnArea.setEditable(false); + warnArea.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + + repair.add(warnArea); + header.add(repair); + + JPanel note = new JPanel(new FlowLayout(FlowLayout.LEADING)); + note.setOpaque(false); + note.add(new JLabel(MessageUtils.getLocalizedMessage("checkidx.label.note"))); + header.add(note); + + JPanel status = new JPanel(new FlowLayout(FlowLayout.LEADING)); + status.setOpaque(false); + status.add(new JLabel(MessageUtils.getLocalizedMessage("label.status"))); + statusLbl.setText("Idle"); + status.add(statusLbl); + indicatorLbl.setVisible(false); + status.add(indicatorLbl); + header.add(status); + + panel.add(header, BorderLayout.PAGE_START); + + logArea.setText(""); + panel.add(new JScrollPane(logArea), BorderLayout.CENTER); + + return panel; + } + + private class Observer implements IndexObserver, DirectoryObserver { + + @Override + public void openIndex(LukeState state) { + lukeState = state; + toolsModel = indexToolsFactory.newInstance(state.getIndexReader(), state.useCompound(), state.keepAllCommits()); + } + + @Override + public void closeIndex() { + close(); + } + + @Override + public void openDirectory(LukeState state) { + lukeState = state; + toolsModel = indexToolsFactory.newInstance(state.getDirectory()); + } + + @Override + public void closeDirectory() { + close(); + } + + private void close() { + toolsModel = null; + } + } + + private class ListenerFunctions { + + void checkIndex(ActionEvent e) { + ExecutorService executor = Executors.newFixedThreadPool(1, new NamedThreadFactory("check-index-dialog-check")); + + SwingWorker task = new SwingWorker() { + + @Override + protected CheckIndex.Status doInBackground() { + setProgress(0); + statusLbl.setText("Running..."); + indicatorLbl.setVisible(true); + TextAreaPrintStream ps; + try { + ps = new TextAreaPrintStream(logArea, new ByteArrayOutputStream(), StandardCharsets.UTF_8, log); + CheckIndex.Status status = toolsModel.checkIndex(ps); + ps.flush(); + return status; + } catch (UnsupportedEncodingException e) { + // will not reach + } catch (Exception e) { + statusLbl.setText(MessageUtils.getLocalizedMessage("message.error.unknown")); + throw e; + } finally { + setProgress(100); + } + return null; + } + + @Override + protected void done() { + try { + CheckIndex.Status st = get(); + resultLbl.setText(createResultsMessage(st)); + indicatorLbl.setVisible(false); + statusLbl.setText("Done"); + if (!st.clean) { + repairBtn.setEnabled(true); + } + status = st; + } catch (Exception e) { + log.error(e.getMessage(), e); + statusLbl.setText(MessageUtils.getLocalizedMessage("message.error.unknown")); + } + } + }; + + executor.submit(task); + executor.shutdown(); + } + + private String createResultsMessage(@Nullable CheckIndex.Status status) { + String msg; + if (status == null) { + msg = "?"; + } else if (status.clean) { + msg = "OK"; + } else if (status.toolOutOfDate) { + msg = "ERROR: Can't check - tool out-of-date"; + } else { + StringBuilder sb = new StringBuilder("BAD:"); + if (status.missingSegments) { + sb.append(" Missing segments."); + } + if (status.numBadSegments > 0) { + sb.append(" numBadSegments="); + sb.append(status.numBadSegments); + } + if (status.totLoseDocCount > 0) { + sb.append(" totLoseDocCount="); + sb.append(status.totLoseDocCount); + } + msg = sb.toString(); + } + return msg; + } + + void repairIndex(ActionEvent e) { + if (status == null) { + return; + } + + ExecutorService executor = Executors.newFixedThreadPool(1, new NamedThreadFactory("check-index-dialog-repair")); + + SwingWorker task = new SwingWorker() { + + @Override + protected CheckIndex.Status doInBackground() { + setProgress(0); + statusLbl.setText("Running..."); + indicatorLbl.setVisible(true); + logArea.setText(""); + TextAreaPrintStream ps; + try { + ps = new TextAreaPrintStream(logArea, new ByteArrayOutputStream(), StandardCharsets.UTF_8, log); + toolsModel.repairIndex(status, ps); + statusLbl.setText("Done"); + ps.flush(); + return status; + } catch (UnsupportedEncodingException e) { + // will not occur + } catch (Exception e) { + statusLbl.setText(MessageUtils.getLocalizedMessage("message.error.unknown")); + throw e; + } finally { + setProgress(100); + } + return null; + } + + @Override + protected void done() { + indexHandler.open(lukeState.getIndexPath(), lukeState.getDirImpl()); + logArea.append("Repairing index done."); + resultLbl.setText(""); + indicatorLbl.setVisible(false); + repairBtn.setEnabled(false); + } + }; + + executor.submit(task); + executor.shutdown(); + } + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OpenIndexDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OpenIndexDialogFactory.java new file mode 100644 index 000000000000..ea96324083d8 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OpenIndexDialogFactory.java @@ -0,0 +1,24 @@ +/* + * 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 org.apache.lucene.luke.app.desktop.util.DialogOpener; + +/** Factory of open index dialog */ +public interface OpenIndexDialogFactory extends DialogOpener.DialogFactory { +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OpenIndexDialogFactoryImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OpenIndexDialogFactoryImpl.java new file mode 100644 index 000000000000..d1c4ab837e92 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OpenIndexDialogFactoryImpl.java @@ -0,0 +1,362 @@ +/* + * 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.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JSeparator; +import java.awt.Dialog; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.GridLayout; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.inject.Inject; +import org.apache.lucene.luke.app.DirectoryHandler; +import org.apache.lucene.luke.app.IndexHandler; +import org.apache.lucene.luke.app.desktop.Preferences; +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.models.LukeException; +import org.apache.lucene.store.FSDirectory; +import org.apache.lucene.store.MMapDirectory; +import org.apache.lucene.util.SuppressForbidden; +import org.reflections.Reflections; +import org.reflections.scanners.SubTypesScanner; +import org.reflections.util.ClasspathHelper; +import org.reflections.util.ConfigurationBuilder; +import org.reflections.util.FilterBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Default implementation of {@link OpenIndexDialogFactory} */ +public final class OpenIndexDialogFactoryImpl implements OpenIndexDialogFactory { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private final Preferences prefs; + + private final DirectoryHandler directoryHandler; + + private final IndexHandler indexHandler; + + private final JComboBox idxPathCombo = new JComboBox<>(); + + private final JButton browseBtn = new JButton(); + + private final JCheckBox readOnlyCB = new JCheckBox(); + + private final JComboBox dirImplCombo = new JComboBox<>(); + + private final JCheckBox noReaderCB = new JCheckBox(); + + private final JCheckBox useCompoundCB = new JCheckBox(); + + private final JRadioButton keepLastCommitRB = new JRadioButton(); + + private final JRadioButton keepAllCommitsRB = new JRadioButton(); + + private final ListenerFunctions listeners = new ListenerFunctions(); + + private JDialog dialog; + + @Inject + public OpenIndexDialogFactoryImpl(Preferences prefs, DirectoryHandler directoryHandler, IndexHandler indexHandler) { + this.prefs = prefs; + this.directoryHandler = directoryHandler; + this.indexHandler = indexHandler; + + initialize(); + } + + private void initialize() { + idxPathCombo.setPreferredSize(new Dimension(360, 40)); + + browseBtn.setText(FontUtils.elegantIconHtml("n", MessageUtils.getLocalizedMessage("button.browse"))); + browseBtn.setFont(StyleConstants.FONT_BUTTON_LARGE); + browseBtn.setPreferredSize(new Dimension(120, 40)); + browseBtn.addActionListener(listeners::browseDirectory); + + readOnlyCB.setText(MessageUtils.getLocalizedMessage("openindex.checkbox.readonly")); + readOnlyCB.setSelected(prefs.isReadOnly()); + readOnlyCB.setOpaque(false); + readOnlyCB.addActionListener(listeners::toggleReadOnly); + + for (String clazzName : supportedDirImpls()) { + dirImplCombo.addItem(clazzName); + } + dirImplCombo.setPreferredSize(new Dimension(350, 30)); + dirImplCombo.setSelectedItem(prefs.getDirImpl()); + + noReaderCB.setText(MessageUtils.getLocalizedMessage("openindex.checkbox.no_reader")); + noReaderCB.setSelected(prefs.isNoReader()); + noReaderCB.setOpaque(false); + + useCompoundCB.setText(MessageUtils.getLocalizedMessage("openindex.checkbox.use_compound")); + useCompoundCB.setSelected(prefs.isUseCompound()); + useCompoundCB.setOpaque(false); + + keepLastCommitRB.setText(MessageUtils.getLocalizedMessage("openindex.radio.keep_only_last_commit")); + keepLastCommitRB.setSelected(!prefs.isKeepAllCommits()); + keepLastCommitRB.setOpaque(false); + + keepAllCommitsRB.setText(MessageUtils.getLocalizedMessage("openindex.radio.keep_all_commits")); + keepAllCommitsRB.setSelected(prefs.isKeepAllCommits()); + keepAllCommitsRB.setOpaque(false); + + } + + @Override + 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(10, 10, 10, 10)); + + panel.add(basicSettings()); + panel.add(new JSeparator(JSeparator.HORIZONTAL)); + panel.add(expertSettings()); + panel.add(new JSeparator(JSeparator.HORIZONTAL)); + panel.add(buttons()); + + return panel; + } + + private JPanel basicSettings() { + JPanel panel = new JPanel(new GridLayout(2, 1)); + panel.setOpaque(false); + + JPanel idxPath = new JPanel(new FlowLayout(FlowLayout.LEADING)); + idxPath.setOpaque(false); + idxPath.add(new JLabel(MessageUtils.getLocalizedMessage("openindex.label.index_path"))); + + idxPathCombo.removeAllItems(); + for (String path : prefs.getHistory()) { + idxPathCombo.addItem(path); + } + idxPath.add(idxPathCombo); + + idxPath.add(browseBtn); + + panel.add(idxPath); + + JPanel readOnly = new JPanel(new FlowLayout(FlowLayout.LEADING)); + readOnly.setOpaque(false); + readOnly.add(readOnlyCB); + JLabel roIconLB = new JLabel(FontUtils.elegantIconHtml("")); + readOnly.add(roIconLB); + panel.add(readOnly); + + return panel; + } + + private JPanel expertSettings() { + JPanel panel = new JPanel(new GridLayout(6, 1)); + panel.setOpaque(false); + + JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING)); + header.setOpaque(false); + header.add(new JLabel(MessageUtils.getLocalizedMessage("openindex.label.expert"))); + panel.add(header); + + JPanel dirImpl = new JPanel(new FlowLayout(FlowLayout.LEADING)); + dirImpl.setOpaque(false); + dirImpl.add(new JLabel(MessageUtils.getLocalizedMessage("openindex.label.dir_impl"))); + dirImpl.add(dirImplCombo); + panel.add(dirImpl); + + JPanel noReader = new JPanel(new FlowLayout(FlowLayout.LEADING)); + noReader.setOpaque(false); + noReader.add(noReaderCB); + JLabel noReaderIcon = new JLabel(FontUtils.elegantIconHtml("")); + noReader.add(noReaderIcon); + panel.add(noReader); + + JPanel iwConfig = new JPanel(new FlowLayout(FlowLayout.LEADING)); + iwConfig.setOpaque(false); + iwConfig.add(new JLabel(MessageUtils.getLocalizedMessage("openindex.label.iw_config"))); + panel.add(iwConfig); + + JPanel compound = new JPanel(new FlowLayout(FlowLayout.LEADING)); + compound.setOpaque(false); + compound.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 0)); + compound.add(useCompoundCB); + panel.add(compound); + + JPanel keepCommits = new JPanel(new FlowLayout(FlowLayout.LEADING)); + keepCommits.setOpaque(false); + keepCommits.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 0)); + keepCommits.add(keepLastCommitRB); + keepCommits.add(keepAllCommitsRB); + + ButtonGroup group = new ButtonGroup(); + group.add(keepLastCommitRB); + group.add(keepAllCommitsRB); + + panel.add(keepCommits); + + return panel; + } + + private String[] supportedDirImpls() { + // supports FS-based built-in implementations + Reflections reflections = new Reflections(new ConfigurationBuilder() + .setUrls(ClasspathHelper.forPackage("org.apache.lucene.store")) + .setScanners(new SubTypesScanner()) + .filterInputsBy(new FilterBuilder().include("org\\.apache\\.lucene\\.store.*")) + ); + Set> clazzSet = reflections.getSubTypesOf(FSDirectory.class); + + List clazzNames = new ArrayList<>(); + clazzNames.add(FSDirectory.class.getName()); + clazzNames.add(MMapDirectory.class.getName()); + clazzNames.addAll(clazzSet.stream().map(Class::getName).collect(Collectors.toList())); + + String[] result = new String[clazzNames.size()]; + return clazzNames.toArray(result); + } + + private JPanel buttons() { + JPanel panel = new JPanel(new FlowLayout(FlowLayout.TRAILING)); + panel.setOpaque(false); + panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 10, 20)); + + JButton okBtn = new JButton(MessageUtils.getLocalizedMessage("button.ok")); + okBtn.addActionListener(listeners::openIndexOrDirectory); + panel.add(okBtn); + + JButton cancelBtn = new JButton(MessageUtils.getLocalizedMessage("button.cancel")); + cancelBtn.addActionListener(e -> dialog.dispose()); + panel.add(cancelBtn); + + return panel; + } + + private class ListenerFunctions { + + @SuppressForbidden(reason = "FileChooser#getSelectedFile() returns java.io.File") + void browseDirectory(ActionEvent e) { + JFileChooser fc = new JFileChooser(); + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + fc.setFileHidingEnabled(false); + int retVal = fc.showOpenDialog(dialog); + if (retVal == JFileChooser.APPROVE_OPTION) { + File dir = fc.getSelectedFile(); + idxPathCombo.insertItemAt(dir.getAbsolutePath(), 0); + idxPathCombo.setSelectedIndex(0); + } + } + + void toggleReadOnly(ActionEvent e) { + setWriterConfigEnabled(!isReadOnly()); + } + + private void setWriterConfigEnabled(boolean enable) { + useCompoundCB.setEnabled(enable); + keepLastCommitRB.setEnabled(enable); + keepAllCommitsRB.setEnabled(enable); + } + + void openIndexOrDirectory(ActionEvent e) { + try { + if (directoryHandler.directoryOpened()) { + directoryHandler.close(); + } + if (indexHandler.indexOpened()) { + indexHandler.close(); + } + + String selectedPath = (String) idxPathCombo.getSelectedItem(); + String dirImplClazz = (String) dirImplCombo.getSelectedItem(); + if (selectedPath == null || selectedPath.length() == 0) { + String msg = MessageUtils.getLocalizedMessage("openindex.message.index_path_not_selected"); + log.error(msg); + } else if (isNoReader()) { + directoryHandler.open(selectedPath, dirImplClazz); + } else { + indexHandler.open(selectedPath, dirImplClazz, isReadOnly(), useCompound(), keepAllCommits()); + } + addHistory(selectedPath); + prefs.setIndexOpenerPrefs( + isReadOnly(), dirImplClazz, + isNoReader(), useCompound(), keepAllCommits()); + closeDialog(); + } catch (LukeException ex) { + String message = ex.getMessage() + System.lineSeparator() + "See Logs tab or log file for more details."; + JOptionPane.showMessageDialog(dialog, message, "Invalid index path", JOptionPane.ERROR_MESSAGE); + } catch (Throwable cause) { + JOptionPane.showMessageDialog(dialog, MessageUtils.getLocalizedMessage("message.error.unknown"), "Unknown Error", JOptionPane.ERROR_MESSAGE); + log.error(cause.getMessage(), cause); + } + } + + private boolean isNoReader() { + return noReaderCB.isSelected(); + } + + private boolean isReadOnly() { + return readOnlyCB.isSelected(); + } + + private boolean useCompound() { + return useCompoundCB.isSelected(); + } + + private boolean keepAllCommits() { + return keepAllCommitsRB.isSelected(); + } + + private void closeDialog() { + dialog.dispose(); + } + + private void addHistory(String indexPath) throws IOException { + prefs.addHistory(indexPath); + } + + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OptimizeIndexDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OptimizeIndexDialogFactory.java new file mode 100644 index 000000000000..217786689d84 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OptimizeIndexDialogFactory.java @@ -0,0 +1,24 @@ +/* + * 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 org.apache.lucene.luke.app.desktop.util.DialogOpener; + +/** Factory of optimize index dialog */ +public interface OptimizeIndexDialogFactory extends DialogOpener.DialogFactory { +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OptimizeIndexDialogFactoryImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OptimizeIndexDialogFactoryImpl.java new file mode 100644 index 000000000000..f21b073c59a9 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OptimizeIndexDialogFactoryImpl.java @@ -0,0 +1,256 @@ +/* + * 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.JCheckBox; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.JSpinner; +import javax.swing.JTextArea; +import javax.swing.SpinnerNumberModel; +import javax.swing.SwingWorker; +import java.awt.BorderLayout; +import java.awt.Dialog; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.GridLayout; +import java.awt.Insets; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.io.ByteArrayOutputStream; +import java.io.UnsupportedEncodingException; +import java.lang.invoke.MethodHandles; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import com.google.inject.Inject; +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.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.StyleConstants; +import org.apache.lucene.luke.app.desktop.util.TextAreaPrintStream; +import org.apache.lucene.luke.models.tools.IndexTools; +import org.apache.lucene.luke.models.tools.IndexToolsFactory; +import org.apache.lucene.util.NamedThreadFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Default implementation of {@link OpenIndexDialogFactory} */ +public final class OptimizeIndexDialogFactoryImpl implements OptimizeIndexDialogFactory { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private final Preferences prefs; + + private final IndexToolsFactory indexToolsFactory; + + private final IndexHandler indexHandler; + + private final JCheckBox expungeCB = new JCheckBox(); + + private final JSpinner maxSegSpnr = new JSpinner(); + + private final JLabel statusLbl = new JLabel(); + + private final JLabel indicatorLbl = new JLabel(); + + private final JTextArea logArea = new JTextArea(); + + private final ListenerFunctions listeners = new ListenerFunctions(); + + private JDialog dialog; + + private IndexTools toolsModel; + + @Inject + public OptimizeIndexDialogFactoryImpl(Preferences prefs, IndexToolsFactory indexToolsFactory, IndexHandler indexHandler) { + this.prefs = prefs; + this.indexToolsFactory = indexToolsFactory; + this.indexHandler = indexHandler; + indexHandler.addObserver(new Observer()); + + initialize(); + } + + private void initialize() { + expungeCB.setText(MessageUtils.getLocalizedMessage("optimize.checkbox.expunge")); + expungeCB.setOpaque(false); + + maxSegSpnr.setModel(new SpinnerNumberModel(1, 1, 100, 1)); + maxSegSpnr.setPreferredSize(new Dimension(100, 30)); + + indicatorLbl.setIcon(ImageUtils.createImageIcon("/img/indicator.gif", 20, 20)); + + logArea.setEditable(false); + } + + @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(controller()); + panel.add(new JSeparator(JSeparator.HORIZONTAL)); + panel.add(logs()); + + return panel; + } + + private JPanel controller() { + JPanel panel = new JPanel(new GridLayout(4, 1)); + panel.setOpaque(false); + + JPanel idxPath = new JPanel(new FlowLayout(FlowLayout.LEADING)); + idxPath.setOpaque(false); + idxPath.add(new JLabel(MessageUtils.getLocalizedMessage("optimize.label.index_path"))); + JLabel idxPathLbl = new JLabel(indexHandler.getState().getIndexPath()); + idxPathLbl.setToolTipText(indexHandler.getState().getIndexPath()); + idxPath.add(idxPathLbl); + panel.add(idxPath); + + JPanel expunge = new JPanel(new FlowLayout(FlowLayout.LEADING)); + expunge.setOpaque(false); + + expunge.add(expungeCB); + panel.add(expunge); + + JPanel maxSegs = new JPanel(new FlowLayout(FlowLayout.LEADING)); + maxSegs.setOpaque(false); + maxSegs.add(new JLabel(MessageUtils.getLocalizedMessage("optimize.label.max_segments"))); + maxSegs.add(maxSegSpnr); + panel.add(maxSegs); + + JPanel execButtons = new JPanel(new FlowLayout(FlowLayout.TRAILING)); + execButtons.setOpaque(false); + JButton optimizeBtn = new JButton(FontUtils.elegantIconHtml("", MessageUtils.getLocalizedMessage("optimize.button.optimize"))); + optimizeBtn.setFont(StyleConstants.FONT_BUTTON_LARGE); + optimizeBtn.setMargin(new Insets(3, 0, 3, 0)); + optimizeBtn.addActionListener(listeners::optimize); + execButtons.add(optimizeBtn); + JButton closeBtn = new JButton(MessageUtils.getLocalizedMessage("button.close")); + closeBtn.setFont(StyleConstants.FONT_BUTTON_LARGE); + closeBtn.setMargin(new Insets(3, 0, 3, 0)); + closeBtn.addActionListener(e -> dialog.dispose()); + execButtons.add(closeBtn); + panel.add(execButtons); + + return panel; + } + + private JPanel logs() { + JPanel panel = new JPanel(new BorderLayout()); + panel.setOpaque(false); + + JPanel header = new JPanel(new GridLayout(2, 1)); + header.setOpaque(false); + header.add(new JLabel(MessageUtils.getLocalizedMessage("optimize.label.note"))); + JPanel status = new JPanel(new FlowLayout(FlowLayout.LEADING)); + status.setOpaque(false); + status.add(new JLabel(MessageUtils.getLocalizedMessage("label.status"))); + statusLbl.setText("Idle"); + status.add(statusLbl); + indicatorLbl.setVisible(false); + status.add(indicatorLbl); + header.add(status); + panel.add(header, BorderLayout.PAGE_START); + + logArea.setText(""); + panel.add(new JScrollPane(logArea), BorderLayout.CENTER); + + return panel; + } + + private class ListenerFunctions { + + void optimize(ActionEvent e) { + ExecutorService executor = Executors.newFixedThreadPool(1, new NamedThreadFactory("optimize-index-dialog")); + + SwingWorker task = new SwingWorker() { + + @Override + protected Void doInBackground() { + setProgress(0); + statusLbl.setText("Running..."); + indicatorLbl.setVisible(true); + TextAreaPrintStream ps; + try { + ps = new TextAreaPrintStream(logArea, new ByteArrayOutputStream(), StandardCharsets.UTF_8, log); + toolsModel.optimize(expungeCB.isSelected(), (int) maxSegSpnr.getValue(), ps); + ps.flush(); + } catch (UnsupportedEncodingException e) { + // will not reach + } catch (Exception e) { + statusLbl.setText(MessageUtils.getLocalizedMessage("message.error.unknown")); + throw e; + } finally { + setProgress(100); + } + return null; + } + + @Override + protected void done() { + indicatorLbl.setVisible(false); + statusLbl.setText("Done"); + indexHandler.reOpen(); + } + }; + + executor.submit(task); + executor.shutdown(); + } + + } + + private class Observer implements IndexObserver { + + @Override + public void openIndex(LukeState state) { + toolsModel = indexToolsFactory.newInstance(state.getIndexReader(), state.useCompound(), state.keepAllCommits()); + } + + @Override + public void closeIndex() { + toolsModel = null; + } + + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/package-info.java new file mode 100644 index 000000000000..72a2d3fc7d5c --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/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 menu bar */ +package org.apache.lucene.luke.app.desktop.components.dialog.menubar; \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/package-info.java new file mode 100644 index 000000000000..44ad40b04fd1 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/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 */ +package org.apache.lucene.luke.app.desktop.components.dialog; \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/search/ExplainDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/search/ExplainDialogFactory.java new file mode 100644 index 000000000000..13782953fbd2 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/search/ExplainDialogFactory.java @@ -0,0 +1,28 @@ +/* + * 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.search; + +import org.apache.lucene.luke.app.desktop.util.DialogOpener; +import org.apache.lucene.search.Explanation; + +/** Factory of explain dialog */ +public interface ExplainDialogFactory extends DialogOpener.DialogFactory { + void setDocid(int docid); + + void setExplanation(Explanation explanation); +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/search/ExplainDialogFactoryImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/search/ExplainDialogFactoryImpl.java new file mode 100644 index 000000000000..18f94aaceb11 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/search/ExplainDialogFactoryImpl.java @@ -0,0 +1,174 @@ +/* + * 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.search; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTree; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeCellRenderer; +import java.awt.BorderLayout; +import java.awt.Dialog; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.GridLayout; +import java.awt.Insets; +import java.awt.Toolkit; +import java.awt.Window; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; +import java.util.Objects; +import java.util.stream.IntStream; + +import com.google.inject.Inject; +import org.apache.lucene.luke.app.desktop.Preferences; +import org.apache.lucene.luke.app.desktop.util.FontUtils; +import org.apache.lucene.luke.app.desktop.util.MessageUtils; +import org.apache.lucene.search.Explanation; + +/** Default implementation of {@link ExplainDialogFactory} */ +public final class ExplainDialogFactoryImpl implements ExplainDialogFactory { + + private final Preferences prefs; + + private JDialog dialog; + + private int docid = -1; + + private Explanation explanation; + + @Inject + public ExplainDialogFactoryImpl(Preferences prefs) { + this.prefs = prefs; + } + + @Override + public void setDocid(int docid) { + this.docid = docid; + } + + @Override + public void setExplanation(Explanation explanation) { + this.explanation = explanation; + } + + @Override + public JDialog create(Window owner, String title, int width, int height) { + if (docid < 0 || Objects.isNull(explanation)) { + throw new IllegalStateException("docid and/or explanation 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, 10)); + header.setOpaque(false); + header.add(new JLabel(MessageUtils.getLocalizedMessage("search.explanation.description"))); + header.add(new JLabel(String.valueOf(docid))); + panel.add(header, BorderLayout.PAGE_START); + + JPanel center = new JPanel(new GridLayout(1, 1)); + center.setOpaque(false); + center.add(new JScrollPane(createExplanationTree())); + panel.add(center, 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(explanationToString()); + 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; + } + + private JTree createExplanationTree() { + DefaultMutableTreeNode top = createNode(explanation); + traverse(top, explanation.getDetails()); + + JTree tree = new JTree(top); + tree.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer(); + renderer.setOpenIcon(null); + renderer.setClosedIcon(null); + renderer.setLeafIcon(null); + tree.setCellRenderer(renderer); + // expand all nodes + for (int row = 0; row < tree.getRowCount(); row++) { + tree.expandRow(row); + } + return tree; + } + + private void traverse(DefaultMutableTreeNode parent, Explanation[] explanations) { + for (Explanation explanation : explanations) { + DefaultMutableTreeNode node = createNode(explanation); + parent.add(node); + traverse(node, explanation.getDetails()); + } + } + + private DefaultMutableTreeNode createNode(Explanation explanation) { + return new DefaultMutableTreeNode(format(explanation)); + } + + private String explanationToString() { + StringBuilder sb = new StringBuilder(format(explanation)); + sb.append(System.lineSeparator()); + traverseToCopy(sb, 1, explanation.getDetails()); + return sb.toString(); + } + + private void traverseToCopy(StringBuilder sb, int depth, Explanation[] explanations) { + for (Explanation explanation : explanations) { + IntStream.range(0, depth).forEach(i -> sb.append(" ")); + sb.append(format(explanation)); + sb.append("\n"); + traverseToCopy(sb, depth + 1, explanation.getDetails()); + } + } + + private String format(Explanation explanation) { + return explanation.getValue() + " " + explanation.getDescription(); + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/search/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/search/package-info.java new file mode 100644 index 000000000000..7af5fb1f80b4 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/search/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 Search tab */ +package org.apache.lucene.luke.app.desktop.components.dialog.search; \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/analysis/CustomAnalyzerPanelOperator.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/analysis/CustomAnalyzerPanelOperator.java new file mode 100644 index 000000000000..54451beaae2e --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/analysis/CustomAnalyzerPanelOperator.java @@ -0,0 +1,45 @@ +/* + * 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.fragments.analysis; + +import java.util.List; +import java.util.Map; + +import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry; +import org.apache.lucene.luke.models.analysis.Analysis; + +/** Operator of the custom analyzer panel */ +public interface CustomAnalyzerPanelOperator extends ComponentOperatorRegistry.ComponentOperator { + void setAnalysisModel(Analysis analysisModel); + + void resetAnalysisComponents(); + + void updateCharFilters(List deletedIndexes); + + void updateTokenFilters(List deletedIndexes); + + Map getCharFilterParams(int index); + + void updateCharFilterParams(int index, Map updatedParams); + + void updateTokenizerParams(Map updatedParams); + + Map getTokenFilterParams(int index); + + void updateTokenFilterParams(int index, Map updatedParams); +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/analysis/CustomAnalyzerPanelProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/analysis/CustomAnalyzerPanelProvider.java new file mode 100644 index 000000000000..52c70bd79d72 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/analysis/CustomAnalyzerPanelProvider.java @@ -0,0 +1,758 @@ +/* + * 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.fragments.analysis; + +import javax.swing.BorderFactory; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.JTextField; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Font; +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.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import com.google.common.collect.ImmutableMap; +import com.google.inject.Inject; +import com.google.inject.Provider; +import org.apache.lucene.luke.app.desktop.MessageBroker; +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.dialog.analysis.EditFiltersDialogFactory; +import org.apache.lucene.luke.app.desktop.components.dialog.analysis.EditFiltersMode; +import org.apache.lucene.luke.app.desktop.components.dialog.analysis.EditParamsDialogFactory; +import org.apache.lucene.luke.app.desktop.components.dialog.analysis.EditParamsMode; +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.ListUtils; +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.lang.Callable; +import org.apache.lucene.luke.models.analysis.Analysis; +import org.apache.lucene.luke.models.analysis.CustomAnalyzerConfig; +import org.apache.lucene.util.SuppressForbidden; + +/** Provider of the custom analyzer panel */ +public final class CustomAnalyzerPanelProvider implements Provider, CustomAnalyzerPanelOperator { + + private final ComponentOperatorRegistry operatorRegistry; + + private final EditParamsDialogFactory editParamsDialogFactory; + + private final EditFiltersDialogFactory editFiltersDialogFactory; + + private final MessageBroker messageBroker; + + private final JTextField confDirTF = new JTextField(); + + private final JFileChooser fileChooser = new JFileChooser(); + + private final JButton confDirBtn = new JButton(); + + private final JButton buildBtn = new JButton(); + + private final JLabel loadJarLbl = new JLabel(); + + private final JList selectedCfList = new JList<>(new String[]{}); + + private final JButton cfEditBtn = new JButton(); + + private final JComboBox cfFactoryCombo = new JComboBox<>(); + + private final JTextField selectedTokTF = new JTextField(); + + private final JButton tokEditBtn = new JButton(); + + private final JComboBox tokFactoryCombo = new JComboBox<>(); + + private final JList selectedTfList = new JList<>(new String[]{}); + + private final JButton tfEditBtn = new JButton(); + + private final JComboBox tfFactoryCombo = new JComboBox<>(); + + private final ListenerFunctions listeners = new ListenerFunctions(); + + private final List> cfParamsList = new ArrayList<>(); + + private final Map tokParams = new HashMap<>(); + + private final List> tfParamsList = new ArrayList<>(); + + private JPanel containerPanel; + + private Analysis analysisModel; + + @Inject + public CustomAnalyzerPanelProvider(ComponentOperatorRegistry operatorRegistry, + EditParamsDialogFactory editParamsDialogFactory, + EditFiltersDialogFactory editFiltersDialogFactory, + MessageBroker messageBroker) { + this.operatorRegistry = operatorRegistry; + this.editParamsDialogFactory = editParamsDialogFactory; + this.editFiltersDialogFactory = editFiltersDialogFactory; + this.messageBroker = messageBroker; + + operatorRegistry.register(CustomAnalyzerPanelOperator.class, this); + + cfFactoryCombo.addActionListener(listeners::addCharFilter); + tokFactoryCombo.addActionListener(listeners::setTokenizer); + tfFactoryCombo.addActionListener(listeners::addTokenFilter); + } + + @Override + public JPanel get() { + if (containerPanel == null) { + containerPanel = new JPanel(); + containerPanel.setOpaque(false); + containerPanel.setLayout(new BorderLayout()); + containerPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); + + containerPanel.add(initCustomAnalyzerHeaderPanel(), BorderLayout.PAGE_START); + containerPanel.add(initCustomAnalyzerChainPanel(), BorderLayout.CENTER); + } + + return containerPanel; + } + + private JPanel initCustomAnalyzerHeaderPanel() { + JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEADING)); + panel.setOpaque(false); + + panel.add(new JLabel(MessageUtils.getLocalizedMessage("analysis.label.config_dir"))); + confDirTF.setColumns(30); + confDirTF.setPreferredSize(new Dimension(200, 30)); + panel.add(confDirTF); + confDirBtn.setText(FontUtils.elegantIconHtml("n", MessageUtils.getLocalizedMessage("analysis.button.browse"))); + confDirBtn.setFont(StyleConstants.FONT_BUTTON_LARGE); + confDirBtn.setMargin(new Insets(3, 3, 3, 3)); + confDirBtn.addActionListener(listeners::chooseConfigDir); + panel.add(confDirBtn); + buildBtn.setText(FontUtils.elegantIconHtml("", MessageUtils.getLocalizedMessage("analysis.button.build_analyzser"))); + buildBtn.setFont(StyleConstants.FONT_BUTTON_LARGE); + buildBtn.setMargin(new Insets(3, 3, 3, 3)); + buildBtn.addActionListener(listeners::buildAnalyzer); + panel.add(buildBtn); + loadJarLbl.setText(MessageUtils.getLocalizedMessage("analysis.hyperlink.load_jars")); + loadJarLbl.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + listeners.loadExternalJars(e); + } + }); + panel.add(FontUtils.toLinkText(loadJarLbl)); + + return panel; + } + + private JPanel initCustomAnalyzerChainPanel() { + JPanel panel = new JPanel(new GridLayout(1, 1)); + panel.setOpaque(false); + panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); + + panel.add(initCustomChainConfigPanel()); + + return panel; + } + + private JPanel initCustomChainConfigPanel() { + JPanel panel = new JPanel(new GridBagLayout()); + panel.setOpaque(false); + panel.setBorder(BorderFactory.createLineBorder(Color.black)); + + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + + GridBagConstraints sepc = new GridBagConstraints(); + sepc.fill = GridBagConstraints.HORIZONTAL; + sepc.weightx = 1.0; + sepc.gridwidth = GridBagConstraints.REMAINDER; + + // char filters + JLabel cfLbl = new JLabel(MessageUtils.getLocalizedMessage("analysis_custom.label.charfilters")); + cfLbl.setBorder(BorderFactory.createEmptyBorder(3, 10, 3, 3)); + c.gridx = 0; + c.gridy = 0; + c.gridwidth = 1; + c.gridheight = 1; + c.weightx = 0.1; + c.weighty = 0.5; + c.anchor = GridBagConstraints.CENTER; + panel.add(cfLbl, c); + + c.gridx = 1; + c.gridy = 0; + c.gridwidth = 1; + c.gridheight = 1; + c.weightx = 0.1; + c.weighty = 0.5; + c.anchor = GridBagConstraints.LINE_END; + panel.add(new JLabel(MessageUtils.getLocalizedMessage("analysis_custom.label.selected")), c); + + selectedCfList.setVisibleRowCount(1); + selectedCfList.setFont(new Font(selectedCfList.getFont().getFontName(), Font.PLAIN, 15)); + JScrollPane selectedPanel = new JScrollPane(selectedCfList); + c.gridx = 2; + c.gridy = 0; + c.gridwidth = 5; + c.gridheight = 1; + c.weightx = 0.5; + c.weighty = 0.5; + c.anchor = GridBagConstraints.LINE_END; + panel.add(selectedPanel, c); + + cfEditBtn.setText(FontUtils.elegantIconHtml("j", MessageUtils.getLocalizedMessage("analysis_custom.label.edit"))); + cfEditBtn.setMargin(new Insets(2, 4, 2, 4)); + cfEditBtn.setEnabled(false); + cfEditBtn.addActionListener(listeners::editCharFilters); + c.fill = GridBagConstraints.NONE; + c.gridx = 7; + c.gridy = 0; + c.gridwidth = 1; + c.gridheight = 1; + c.weightx = 0.1; + c.weighty = 0.5; + c.anchor = GridBagConstraints.CENTER; + panel.add(cfEditBtn, c); + + JLabel cfAddLabel = new JLabel(FontUtils.elegantIconHtml("L", MessageUtils.getLocalizedMessage("analysis_custom.label.add"))); + cfAddLabel.setHorizontalAlignment(JLabel.LEFT); + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx = 1; + c.gridy = 2; + c.gridwidth = 1; + c.gridheight = 1; + c.weightx = 0.1; + c.weighty = 0.5; + c.anchor = GridBagConstraints.LINE_END; + panel.add(cfAddLabel, c); + + c.gridx = 2; + c.gridy = 2; + c.gridwidth = 5; + c.gridheight = 1; + c.weightx = 0.5; + c.weighty = 0.5; + c.anchor = GridBagConstraints.LINE_END; + panel.add(cfFactoryCombo, c); + + // separator + sepc.gridx = 0; + sepc.gridy = 3; + sepc.anchor = GridBagConstraints.LINE_START; + panel.add(new JSeparator(JSeparator.HORIZONTAL), sepc); + + // tokenizer + JLabel tokLabel = new JLabel(MessageUtils.getLocalizedMessage("analysis_custom.label.tokenizer")); + tokLabel.setBorder(BorderFactory.createEmptyBorder(3, 10, 3, 3)); + c.gridx = 0; + c.gridy = 4; + c.gridwidth = 1; + c.gridheight = 2; + c.weightx = 0.1; + c.weighty = 0.5; + c.anchor = GridBagConstraints.CENTER; + panel.add(tokLabel, c); + + c.gridx = 1; + c.gridy = 4; + c.gridwidth = 1; + c.gridheight = 1; + c.weightx = 0.1; + c.weighty = 0.5; + c.anchor = GridBagConstraints.LINE_END; + panel.add(new JLabel(MessageUtils.getLocalizedMessage("analysis_custom.label.selected")), c); + + selectedTokTF.setColumns(15); + selectedTokTF.setFont(new Font(selectedTokTF.getFont().getFontName(), Font.PLAIN, 15)); + selectedTokTF.setBorder(BorderFactory.createLineBorder(Color.gray)); + selectedTokTF.setText("standard"); + selectedTokTF.setEditable(false); + c.gridx = 2; + c.gridy = 4; + c.gridwidth = 5; + c.gridheight = 1; + c.weightx = 0.5; + c.weighty = 0.5; + c.anchor = GridBagConstraints.LINE_END; + panel.add(selectedTokTF, c); + + tokEditBtn.setText(FontUtils.elegantIconHtml("j", MessageUtils.getLocalizedMessage("analysis_custom.label.edit"))); + tokEditBtn.setMargin(new Insets(2, 4, 2, 4)); + tokEditBtn.addActionListener(listeners::editTokenizer); + c.fill = GridBagConstraints.NONE; + c.gridx = 7; + c.gridy = 4; + c.gridwidth = 2; + c.gridheight = 1; + c.weightx = 0.1; + c.weighty = 0.5; + c.anchor = GridBagConstraints.CENTER; + panel.add(tokEditBtn, c); + + JLabel setTokLabel = new JLabel(FontUtils.elegantIconHtml("", MessageUtils.getLocalizedMessage("analysis_custom.label.set"))); + setTokLabel.setHorizontalAlignment(JLabel.LEFT); + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx = 1; + c.gridy = 6; + c.gridwidth = 1; + c.gridheight = 1; + c.weightx = 0.1; + c.weighty = 0.5; + c.anchor = GridBagConstraints.LINE_END; + panel.add(setTokLabel, c); + + c.gridx = 2; + c.gridy = 6; + c.gridwidth = 5; + c.gridheight = 1; + c.weightx = 0.5; + c.weighty = 0.5; + c.anchor = GridBagConstraints.LINE_END; + panel.add(tokFactoryCombo, c); + + // separator + sepc.gridx = 0; + sepc.gridy = 7; + sepc.anchor = GridBagConstraints.LINE_START; + panel.add(new JSeparator(JSeparator.HORIZONTAL), sepc); + + // token filters + JLabel tfLbl = new JLabel(MessageUtils.getLocalizedMessage("analysis_custom.label.tokenfilters")); + tfLbl.setBorder(BorderFactory.createEmptyBorder(3, 10, 3, 3)); + c.gridx = 0; + c.gridy = 8; + c.gridwidth = 1; + c.gridheight = 2; + c.weightx = 0.1; + c.weighty = 0.5; + c.anchor = GridBagConstraints.CENTER; + panel.add(tfLbl, c); + + c.gridx = 1; + c.gridy = 8; + c.gridwidth = 1; + c.gridheight = 1; + c.weightx = 0.1; + c.weighty = 0.5; + c.anchor = GridBagConstraints.LINE_END; + panel.add(new JLabel(MessageUtils.getLocalizedMessage("analysis_custom.label.selected")), c); + + selectedTfList.setVisibleRowCount(1); + selectedTfList.setFont(new Font(selectedTfList.getFont().getFontName(), Font.PLAIN, 15)); + JScrollPane selectedTfPanel = new JScrollPane(selectedTfList); + c.gridx = 2; + c.gridy = 8; + c.gridwidth = 5; + c.gridheight = 1; + c.weightx = 0.5; + c.weighty = 0.5; + c.anchor = GridBagConstraints.LINE_END; + panel.add(selectedTfPanel, c); + + tfEditBtn.setText(FontUtils.elegantIconHtml("j", MessageUtils.getLocalizedMessage("analysis_custom.label.edit"))); + tfEditBtn.setMargin(new Insets(2, 4, 2, 4)); + tfEditBtn.setEnabled(false); + tfEditBtn.addActionListener(listeners::editTokenFilters); + c.fill = GridBagConstraints.NONE; + c.gridx = 7; + c.gridy = 8; + c.gridwidth = 2; + c.gridheight = 1; + c.weightx = 0.1; + c.weighty = 0.5; + c.anchor = GridBagConstraints.CENTER; + panel.add(tfEditBtn, c); + + JLabel tfAddLabel = new JLabel(FontUtils.elegantIconHtml("L", MessageUtils.getLocalizedMessage("analysis_custom.label.add"))); + tfAddLabel.setHorizontalAlignment(JLabel.LEFT); + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx = 1; + c.gridy = 10; + c.gridwidth = 1; + c.gridheight = 1; + c.weightx = 0.1; + c.weighty = 0.5; + c.anchor = GridBagConstraints.LINE_END; + panel.add(tfAddLabel, c); + + c.gridx = 2; + c.gridy = 10; + c.gridwidth = 5; + c.gridheight = 1; + c.weightx = 0.5; + c.weighty = 0.5; + c.anchor = GridBagConstraints.LINE_END; + panel.add(tfFactoryCombo, c); + + return panel; + } + + // control methods + + @SuppressForbidden(reason = "JFilechooser#getSelectedFile() returns java.io.File") + private void chooseConfigDir() { + fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + + int ret = fileChooser.showOpenDialog(containerPanel); + if (ret == JFileChooser.APPROVE_OPTION) { + File dir = fileChooser.getSelectedFile(); + confDirTF.setText(dir.getAbsolutePath()); + } + } + + @SuppressForbidden(reason = "JFilechooser#getSelectedFiles() returns java.io.File[]") + private void loadExternalJars() { + fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + fileChooser.setMultiSelectionEnabled(true); + + int ret = fileChooser.showOpenDialog(containerPanel); + if (ret == JFileChooser.APPROVE_OPTION) { + File[] files = fileChooser.getSelectedFiles(); + analysisModel.addExternalJars(Arrays.stream(files).map(File::getAbsolutePath).collect(Collectors.toList())); + operatorRegistry.get(CustomAnalyzerPanelOperator.class).ifPresent(operator -> + operator.resetAnalysisComponents() + ); + messageBroker.showStatusMessage("External jars were added."); + } + } + + + private void buildAnalyzer() { + List charFilters = ListUtils.getAllItems(selectedCfList); + assert charFilters.size() == cfParamsList.size(); + + List tokenFilters = ListUtils.getAllItems(selectedTfList); + assert tokenFilters.size() == tfParamsList.size(); + + String tokenizerName = selectedTokTF.getText(); + CustomAnalyzerConfig.Builder builder = + new CustomAnalyzerConfig.Builder(tokenizerName, tokParams).configDir(confDirTF.getText()); + IntStream.range(0, charFilters.size()).forEach(i -> + builder.addCharFilterConfig(charFilters.get(i), cfParamsList.get(i)) + ); + IntStream.range(0, tokenFilters.size()).forEach(i -> + builder.addTokenFilterConfig(tokenFilters.get(i), tfParamsList.get(i)) + ); + CustomAnalyzerConfig config = builder.build(); + + operatorRegistry.get(AnalysisTabOperator.class).ifPresent(operator -> { + operator.setAnalyzerByCustomConfiguration(config); + messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("analysis.message.build_success")); + buildBtn.setEnabled(false); + }); + + } + + private void addCharFilter() { + if (Objects.isNull(cfFactoryCombo.getSelectedItem()) || cfFactoryCombo.getSelectedItem() == "") { + return; + } + + int targetIndex = selectedCfList.getModel().getSize(); + String selectedItem = (String) cfFactoryCombo.getSelectedItem(); + List updatedList = ListUtils.getAllItems(selectedCfList); + updatedList.add(selectedItem); + cfParamsList.add(new HashMap<>()); + + assert selectedCfList.getModel().getSize() == cfParamsList.size(); + + showEditParamsDialog(MessageUtils.getLocalizedMessage("analysis.dialog.title.char_filter_params"), + EditParamsMode.CHARFILTER, targetIndex, selectedItem, cfParamsList.get(cfParamsList.size() - 1), + () -> { + selectedCfList.setModel(new DefaultComboBoxModel<>(updatedList.toArray(new String[0]))); + cfFactoryCombo.setSelectedItem(""); + cfEditBtn.setEnabled(true); + buildBtn.setEnabled(true); + }); + } + + private void setTokenizer() { + if (Objects.isNull(tokFactoryCombo.getSelectedItem()) || tokFactoryCombo.getSelectedItem() == "") { + return; + } + + String selectedItem = (String) tokFactoryCombo.getSelectedItem(); + showEditParamsDialog(MessageUtils.getLocalizedMessage("analysis.dialog.title.tokenizer_params"), + EditParamsMode.TOKENIZER, -1, selectedItem, Collections.emptyMap(), + () -> { + selectedTokTF.setText(selectedItem); + tokFactoryCombo.setSelectedItem(""); + buildBtn.setEnabled(true); + }); + } + + private void addTokenFilter() { + if (Objects.isNull(tfFactoryCombo.getSelectedItem()) || tfFactoryCombo.getSelectedItem() == "") { + return; + } + + int targetIndex = selectedTfList.getModel().getSize(); + String selectedItem = (String) tfFactoryCombo.getSelectedItem(); + List updatedList = ListUtils.getAllItems(selectedTfList); + updatedList.add(selectedItem); + tfParamsList.add(new HashMap<>()); + + assert selectedTfList.getModel().getSize() == tfParamsList.size(); + + showEditParamsDialog(MessageUtils.getLocalizedMessage("analysis.dialog.title.token_filter_params"), + EditParamsMode.TOKENFILTER, targetIndex, selectedItem, tfParamsList.get(tfParamsList.size() - 1), + () -> { + selectedTfList.setModel(new DefaultComboBoxModel<>(updatedList.toArray(new String[updatedList.size()]))); + tfFactoryCombo.setSelectedItem(""); + tfEditBtn.setEnabled(true); + buildBtn.setEnabled(true); + }); + } + + private void showEditParamsDialog(String title, EditParamsMode mode, int targetIndex, String selectedItem, Map params, Callable callback) { + new DialogOpener<>(editParamsDialogFactory).open(title, 400, 300, + (factory) -> { + factory.setMode(mode); + factory.setTargetIndex(targetIndex); + factory.setTarget(selectedItem); + factory.setParams(params); + factory.setCallback(callback); + }); + } + + private void editCharFilters() { + List filters = ListUtils.getAllItems(selectedCfList); + showEditFiltersDialog(EditFiltersMode.CHARFILTER, filters, + () -> { + cfEditBtn.setEnabled(selectedCfList.getModel().getSize() > 0); + buildBtn.setEnabled(true); + }); + } + + private void editTokenizer() { + String selectedItem = selectedTokTF.getText(); + showEditParamsDialog(MessageUtils.getLocalizedMessage("analysis.dialog.title.tokenizer_params"), + EditParamsMode.TOKENIZER, -1, selectedItem, tokParams, () -> { + buildBtn.setEnabled(true); + }); + } + + private void editTokenFilters() { + List filters = ListUtils.getAllItems(selectedTfList); + showEditFiltersDialog(EditFiltersMode.TOKENFILTER, filters, + () -> { + tfEditBtn.setEnabled(selectedTfList.getModel().getSize() > 0); + buildBtn.setEnabled(true); + }); + } + + private void showEditFiltersDialog(EditFiltersMode mode, List selectedFilters, Callable callback) { + String title = (mode == EditFiltersMode.CHARFILTER) ? + MessageUtils.getLocalizedMessage("analysis.dialog.title.selected_char_filter") : + MessageUtils.getLocalizedMessage("analysis.dialog.title.selected_token_filter"); + new DialogOpener<>(editFiltersDialogFactory).open(title, 400, 300, + (factory) -> { + factory.setMode(mode); + factory.setSelectedFilters(selectedFilters); + factory.setCallback(callback); + }); + } + + @Override + public void setAnalysisModel(Analysis model) { + analysisModel = model; + } + + @Override + public void resetAnalysisComponents() { + setAvailableCharFilterFactories(); + setAvailableTokenizerFactories(); + setAvailableTokenFilterFactories(); + buildBtn.setEnabled(true); + } + + private void setAvailableCharFilterFactories() { + Collection charFilters = analysisModel.getAvailableCharFilters(); + String[] charFilterNames = new String[charFilters.size() + 1]; + charFilterNames[0] = ""; + System.arraycopy(charFilters.toArray(new String[0]), 0, charFilterNames, 1, charFilters.size()); + cfFactoryCombo.setModel(new DefaultComboBoxModel<>(charFilterNames)); + } + + private void setAvailableTokenizerFactories() { + Collection tokenizers = analysisModel.getAvailableTokenizers(); + String[] tokenizerNames = new String[tokenizers.size() + 1]; + tokenizerNames[0] = ""; + System.arraycopy(tokenizers.toArray(new String[0]), 0, tokenizerNames, 1, tokenizers.size()); + tokFactoryCombo.setModel(new DefaultComboBoxModel<>(tokenizerNames)); + } + + private void setAvailableTokenFilterFactories() { + Collection tokenFilters = analysisModel.getAvailableTokenFilters(); + String[] tokenFilterNames = new String[tokenFilters.size() + 1]; + tokenFilterNames[0] = ""; + System.arraycopy(tokenFilters.toArray(new String[0]), 0, tokenFilterNames, 1, tokenFilters.size()); + tfFactoryCombo.setModel(new DefaultComboBoxModel<>(tokenFilterNames)); + } + + @Override + public void updateCharFilters(List deletedIndexes) { + // update filters + List filters = ListUtils.getAllItems(selectedCfList); + String[] updatedFilters = IntStream.range(0, filters.size()) + .filter(i -> !deletedIndexes.contains(i)) + .mapToObj(filters::get) + .toArray(String[]::new); + selectedCfList.setModel(new DefaultComboBoxModel<>(updatedFilters)); + // update parameters map for each filter + List> updatedParamList = IntStream.range(0, cfParamsList.size()) + .filter(i -> !deletedIndexes.contains(i)) + .mapToObj(cfParamsList::get) + .collect(Collectors.toList()); + cfParamsList.clear(); + cfParamsList.addAll(updatedParamList); + assert selectedCfList.getModel().getSize() == cfParamsList.size(); + } + + @Override + public void updateTokenFilters(List deletedIndexes) { + // update filters + List filters = ListUtils.getAllItems(selectedTfList); + String[] updatedFilters = IntStream.range(0, filters.size()) + .filter(i -> !deletedIndexes.contains(i)) + .mapToObj(filters::get) + .toArray(String[]::new); + selectedTfList.setModel(new DefaultComboBoxModel<>(updatedFilters)); + // update parameters map for each filter + List> updatedParamList = IntStream.range(0, tfParamsList.size()) + .filter(i -> !deletedIndexes.contains(i)) + .mapToObj(tfParamsList::get) + .collect(Collectors.toList()); + tfParamsList.clear(); + tfParamsList.addAll(updatedParamList); + assert selectedTfList.getModel().getSize() == tfParamsList.size(); + } + + @Override + public Map getCharFilterParams(int index) { + if (index < 0 || index > cfParamsList.size()) { + throw new IllegalArgumentException(); + } + return ImmutableMap.copyOf(cfParamsList.get(index)); + } + + @Override + public void updateCharFilterParams(int index, Map updatedParams) { + if (index < 0 || index > cfParamsList.size()) { + throw new IllegalArgumentException(); + } + if (index == cfParamsList.size()) { + cfParamsList.add(new HashMap<>()); + } + cfParamsList.get(index).clear(); + cfParamsList.get(index).putAll(updatedParams); + } + + @Override + public void updateTokenizerParams(Map updatedParams) { + tokParams.clear(); + tokParams.putAll(updatedParams); + } + + @Override + public Map getTokenFilterParams(int index) { + if (index < 0 || index > tfParamsList.size()) { + throw new IllegalArgumentException(); + } + return ImmutableMap.copyOf(tfParamsList.get(index)); + } + + @Override + public void updateTokenFilterParams(int index, Map updatedParams) { + if (index < 0 || index > tfParamsList.size()) { + throw new IllegalArgumentException(); + } + if (index == tfParamsList.size()) { + tfParamsList.add(new HashMap<>()); + } + tfParamsList.get(index).clear(); + tfParamsList.get(index).putAll(updatedParams); + } + + private class ListenerFunctions { + + void chooseConfigDir(ActionEvent e) { + CustomAnalyzerPanelProvider.this.chooseConfigDir(); + } + + void loadExternalJars(MouseEvent e) { + CustomAnalyzerPanelProvider.this.loadExternalJars(); + } + + void buildAnalyzer(ActionEvent e) { + CustomAnalyzerPanelProvider.this.buildAnalyzer(); + } + + void addCharFilter(ActionEvent e) { + CustomAnalyzerPanelProvider.this.addCharFilter(); + } + + void setTokenizer(ActionEvent e) { + CustomAnalyzerPanelProvider.this.setTokenizer(); + } + + void addTokenFilter(ActionEvent e) { + CustomAnalyzerPanelProvider.this.addTokenFilter(); + } + + void editCharFilters(ActionEvent e) { + CustomAnalyzerPanelProvider.this.editCharFilters(); + } + + void editTokenizer(ActionEvent e) { + CustomAnalyzerPanelProvider.this.editTokenizer(); + } + + void editTokenFilters(ActionEvent e) { + CustomAnalyzerPanelProvider.this.editTokenFilters(); + } + + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/analysis/PresetAnalyzerPanelOperator.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/analysis/PresetAnalyzerPanelOperator.java new file mode 100644 index 000000000000..856de6357e12 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/analysis/PresetAnalyzerPanelOperator.java @@ -0,0 +1,30 @@ +/* + * 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.fragments.analysis; + +import java.util.Collection; + +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry; + +/** Operator of the preset analyzer panel */ +public interface PresetAnalyzerPanelOperator extends ComponentOperatorRegistry.ComponentOperator { + void setPresetAnalyzers(Collection> presetAnalyzers); + + void setSelectedAnalyzer(Class analyzer); +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/analysis/PresetAnalyzerPanelProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/analysis/PresetAnalyzerPanelProvider.java new file mode 100644 index 000000000000..4db93bcd6722 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/analysis/PresetAnalyzerPanelProvider.java @@ -0,0 +1,98 @@ +/* + * 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.fragments.analysis; + +import javax.swing.BorderFactory; +import javax.swing.ComboBoxModel; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.util.Collection; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import org.apache.lucene.analysis.Analyzer; +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.util.MessageUtils; + +/** Provider of the preset analyzer panel */ +public final class PresetAnalyzerPanelProvider implements Provider, PresetAnalyzerPanelOperator { + + private final ComponentOperatorRegistry operatorRegistry; + + private final JComboBox analyzersCB = new JComboBox<>(); + + private final ListenerFunctions listeners = new ListenerFunctions(); + + @Inject + public PresetAnalyzerPanelProvider(ComponentOperatorRegistry operatorRegistry) { + this.operatorRegistry = operatorRegistry; + operatorRegistry.register(PresetAnalyzerPanelOperator.class, this); + } + + @Override + public JPanel get() { + JPanel panel = new JPanel(new BorderLayout()); + panel.setOpaque(false); + panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); + + JLabel header = new JLabel(MessageUtils.getLocalizedMessage("analysis_preset.label.preset")); + panel.add(header, BorderLayout.PAGE_START); + + JPanel center = new JPanel(new FlowLayout(FlowLayout.LEADING)); + center.setOpaque(false); + center.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + center.setPreferredSize(new Dimension(400, 40)); + analyzersCB.addActionListener(listeners::setAnalyzer); + center.add(analyzersCB); + panel.add(center, BorderLayout.CENTER); + + return panel; + } + + // control methods + + @Override + public void setPresetAnalyzers(Collection> presetAnalyzers) { + String[] analyzerNames = presetAnalyzers.stream().map(Class::getName).toArray(String[]::new); + ComboBoxModel model = new DefaultComboBoxModel<>(analyzerNames); + analyzersCB.setModel(model); + } + + @Override + public void setSelectedAnalyzer(Class analyzer) { + analyzersCB.setSelectedItem(analyzer.getName()); + } + + private class ListenerFunctions { + + void setAnalyzer(ActionEvent e) { + operatorRegistry.get(AnalysisTabOperator.class).ifPresent(operator -> + operator.setAnalyzerByType((String) analyzersCB.getSelectedItem()) + ); + } + + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/analysis/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/analysis/package-info.java new file mode 100644 index 000000000000..20cbe7b84f55 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/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. + */ + +/** UI parts embedded in the Analysis tab */ +package org.apache.lucene.luke.app.desktop.components.fragments.analysis; \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/package-info.java new file mode 100644 index 000000000000..382d73aaf691 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/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. + */ + +/** UI parts embedded in tabs */ +package org.apache.lucene.luke.app.desktop.components.fragments; \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/AnalyzerPaneProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/AnalyzerPaneProvider.java new file mode 100644 index 000000000000..6d87dac84187 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/AnalyzerPaneProvider.java @@ -0,0 +1,205 @@ +/* + * 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.fragments.search; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.DefaultListModel; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.JTextField; +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.Insets; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +import com.google.inject.Inject; +import com.google.inject.Provider; +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.components.ComponentOperatorRegistry; +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.util.FontUtils; +import org.apache.lucene.luke.app.desktop.util.MessageUtils; + +/** Provider of the Analyzer pane (tab) */ +public final class AnalyzerPaneProvider implements Provider, AnalyzerTabOperator { + + private final TabSwitcherProxy tabSwitcher; + + private final JLabel analyzerNameLbl = new JLabel(StandardAnalyzer.class.getName()); + + private final JList charFilterList = new JList<>(); + + private final JTextField tokenizerTF = new JTextField(); + + private final JList tokenFilterList = new JList<>(); + + @Inject + public AnalyzerPaneProvider(TabSwitcherProxy tabSwitcher, + ComponentOperatorRegistry operatorRegistry) { + this.tabSwitcher = tabSwitcher; + + operatorRegistry.register(AnalyzerTabOperator.class, this); + } + + @Override + public JScrollPane get() { + JPanel panel = new JPanel(); + panel.setOpaque(false); + panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); + panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); + + panel.add(initAnalyzerNamePanel()); + panel.add(new JSeparator(JSeparator.HORIZONTAL)); + panel.add(initAnalysisChainPanel()); + + tokenizerTF.setEditable(false); + + JScrollPane scrollPane = new JScrollPane(panel); + scrollPane.setOpaque(false); + scrollPane.getViewport().setOpaque(false); + return scrollPane; + } + + private JPanel initAnalyzerNamePanel() { + JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEADING)); + panel.setOpaque(false); + + panel.add(new JLabel(MessageUtils.getLocalizedMessage("search_analyzer.label.name"))); + + panel.add(analyzerNameLbl); + + JLabel changeLbl = new JLabel(MessageUtils.getLocalizedMessage("search_analyzer.hyperlink.change")); + changeLbl.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + tabSwitcher.switchTab(TabbedPaneProvider.Tab.ANALYZER); + } + }); + panel.add(FontUtils.toLinkText(changeLbl)); + + return panel; + } + + private JPanel initAnalysisChainPanel() { + JPanel panel = new JPanel(new BorderLayout()); + panel.setOpaque(false); + + JPanel top = new JPanel(new FlowLayout(FlowLayout.LEADING)); + top.setOpaque(false); + top.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + top.add(new JLabel(MessageUtils.getLocalizedMessage("search_analyzer.label.chain"))); + panel.add(top, BorderLayout.PAGE_START); + + JPanel center = new JPanel(new GridBagLayout()); + center.setOpaque(false); + + GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.BOTH; + c.insets = new Insets(5, 5, 5, 5); + + c.gridx = 0; + c.gridy = 0; + c.weightx = 0.1; + center.add(new JLabel(MessageUtils.getLocalizedMessage("search_analyzer.label.charfilters")), c); + + charFilterList.setVisibleRowCount(3); + JScrollPane charFilterSP = new JScrollPane(charFilterList); + c.gridx = 1; + c.gridy = 0; + c.weightx = 0.5; + center.add(charFilterSP, c); + + c.gridx = 0; + c.gridy = 1; + c.weightx = 0.1; + center.add(new JLabel(MessageUtils.getLocalizedMessage("search_analyzer.label.tokenizer")), c); + + tokenizerTF.setColumns(30); + tokenizerTF.setPreferredSize(new Dimension(400, 25)); + tokenizerTF.setBorder(BorderFactory.createLineBorder(Color.gray)); + c.gridx = 1; + c.gridy = 1; + c.weightx = 0.5; + center.add(tokenizerTF, c); + + c.gridx = 0; + c.gridy = 2; + c.weightx = 0.1; + center.add(new JLabel(MessageUtils.getLocalizedMessage("search_analyzer.label.tokenfilters")), c); + + tokenFilterList.setVisibleRowCount(3); + JScrollPane tokenFilterSP = new JScrollPane(tokenFilterList); + c.gridx = 1; + c.gridy = 2; + c.weightx = 0.5; + center.add(tokenFilterSP, c); + + panel.add(center, BorderLayout.CENTER); + + return panel; + } + + @Override + public void setAnalyzer(Analyzer analyzer) { + analyzerNameLbl.setText(analyzer.getClass().getName()); + + if (analyzer instanceof CustomAnalyzer) { + CustomAnalyzer customAnalyzer = (CustomAnalyzer) analyzer; + + DefaultListModel charFilterListModel = new DefaultListModel<>(); + customAnalyzer.getCharFilterFactories().stream() + .map(f -> f.getClass().getSimpleName()) + .forEach(charFilterListModel::addElement); + charFilterList.setModel(charFilterListModel); + + tokenizerTF.setText(customAnalyzer.getTokenizerFactory().getClass().getSimpleName()); + + DefaultListModel tokenFilterListModel = new DefaultListModel<>(); + customAnalyzer.getTokenFilterFactories().stream() + .map(f -> f.getClass().getSimpleName()) + .forEach(tokenFilterListModel::addElement); + tokenFilterList.setModel(tokenFilterListModel); + + charFilterList.setBackground(Color.white); + tokenizerTF.setBackground(Color.white); + tokenFilterList.setBackground(Color.white); + } else { + charFilterList.setModel(new DefaultListModel<>()); + tokenizerTF.setText(""); + tokenFilterList.setModel(new DefaultListModel<>()); + + charFilterList.setBackground(Color.lightGray); + tokenizerTF.setBackground(Color.lightGray); + tokenFilterList.setBackground(Color.lightGray); + } + } + + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/AnalyzerTabOperator.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/AnalyzerTabOperator.java new file mode 100644 index 000000000000..fc3a90b786ba --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/AnalyzerTabOperator.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.fragments.search; + +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry; + +/** Operator of the Analyzer tab */ +public interface AnalyzerTabOperator extends ComponentOperatorRegistry.ComponentOperator { + void setAnalyzer(Analyzer analyzer); +} + diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/FieldValuesPaneProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/FieldValuesPaneProvider.java new file mode 100644 index 000000000000..c199823ba064 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/FieldValuesPaneProvider.java @@ -0,0 +1,211 @@ +/* + * 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.fragments.search; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.event.TableModelEvent; +import java.awt.BorderLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import com.google.inject.Inject; +import com.google.inject.Provider; +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.util.MessageUtils; +import org.apache.lucene.luke.app.desktop.util.TableUtils; + +/** Provider of the FieldValues pane (tab) */ +public final class FieldValuesPaneProvider implements Provider, FieldValuesTabOperator { + + private final JCheckBox loadAllCB = new JCheckBox(); + + private final JTable fieldsTable = new JTable(); + + private ListenerFunctions listners = new ListenerFunctions(); + + @Inject + public FieldValuesPaneProvider(ComponentOperatorRegistry operatorRegistry) { + operatorRegistry.register(FieldValuesTabOperator.class, this); + } + + @Override + public JScrollPane get() { + JPanel panel = new JPanel(); + panel.setOpaque(false); + panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); + panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + panel.add(initFieldsConfigPanel()); + + JScrollPane scrollPane = new JScrollPane(panel); + scrollPane.setOpaque(false); + scrollPane.getViewport().setOpaque(false); + return scrollPane; + } + + private JPanel initFieldsConfigPanel() { + JPanel panel = new JPanel(new BorderLayout()); + panel.setOpaque(false); + + JPanel header = new JPanel(new GridLayout(1, 2)); + header.setOpaque(false); + header.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); + header.add(new JLabel(MessageUtils.getLocalizedMessage("search_values.label.description"))); + loadAllCB.setText(MessageUtils.getLocalizedMessage("search_values.checkbox.load_all")); + loadAllCB.setSelected(true); + loadAllCB.setOpaque(false); + loadAllCB.addActionListener(listners::loadAllFields); + header.add(loadAllCB); + panel.add(header, BorderLayout.PAGE_START); + + TableUtils.setupTable(fieldsTable, ListSelectionModel.SINGLE_SELECTION, new FieldsTableModel(), null, + FieldsTableModel.Column.LOAD.getColumnWidth()); + fieldsTable.setShowGrid(true); + fieldsTable.setPreferredScrollableViewportSize(fieldsTable.getPreferredSize()); + panel.add(new JScrollPane(fieldsTable), BorderLayout.CENTER); + + return panel; + } + + @Override + public void setFields(Collection fields) { + fieldsTable.setModel(new FieldsTableModel(fields)); + fieldsTable.getColumnModel().getColumn(FieldsTableModel.Column.LOAD.getIndex()).setMinWidth(FieldsTableModel.Column.LOAD.getColumnWidth()); + fieldsTable.getColumnModel().getColumn(FieldsTableModel.Column.LOAD.getIndex()).setMaxWidth(FieldsTableModel.Column.LOAD.getColumnWidth()); + fieldsTable.getModel().addTableModelListener(listners::tableDataChenged); + } + + @Override + public Set getFieldsToLoad() { + Set fieldsToLoad = new HashSet<>(); + for (int row = 0; row < fieldsTable.getRowCount(); row++) { + boolean loaded = (boolean) fieldsTable.getValueAt(row, FieldsTableModel.Column.LOAD.getIndex()); + if (loaded) { + fieldsToLoad.add((String) fieldsTable.getValueAt(row, FieldsTableModel.Column.FIELD.getIndex())); + } + } + return fieldsToLoad; + } + + class ListenerFunctions { + + void loadAllFields(ActionEvent e) { + for (int i = 0; i < fieldsTable.getModel().getRowCount(); i++) { + if (loadAllCB.isSelected()) { + fieldsTable.setValueAt(true, i, FieldsTableModel.Column.LOAD.getIndex()); + } else { + fieldsTable.setValueAt(false, i, FieldsTableModel.Column.LOAD.getIndex()); + } + } + } + + void tableDataChenged(TableModelEvent e) { + int row = e.getFirstRow(); + int col = e.getColumn(); + if (col == FieldsTableModel.Column.LOAD.getIndex()) { + boolean isLoad = (boolean) fieldsTable.getModel().getValueAt(row, col); + if (!isLoad) { + loadAllCB.setSelected(false); + } + } + } + } + +} + +final class FieldsTableModel extends TableModelBase { + + enum Column implements TableColumnInfo { + LOAD("Load", 0, Boolean.class, 50), + FIELD("Field", 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; + } + } + + FieldsTableModel() { + super(); + } + + FieldsTableModel(Collection fields) { + super(fields.size()); + int i = 0; + for (String field : fields) { + data[i][Column.LOAD.getIndex()] = true; + data[i][Column.FIELD.getIndex()] = field; + i++; + } + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return columnIndex == Column.LOAD.getIndex(); + } + + @Override + public void setValueAt(Object value, int rowIndex, int columnIndex) { + data[rowIndex][columnIndex] = value; + fireTableCellUpdated(rowIndex, columnIndex); + } + + @Override + protected Column[] columnInfos() { + return Column.values(); + } +} \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/FieldValuesTabOperator.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/FieldValuesTabOperator.java new file mode 100644 index 000000000000..0b317651c061 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/FieldValuesTabOperator.java @@ -0,0 +1,30 @@ +/* + * 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.fragments.search; + +import java.util.Collection; +import java.util.Set; + +import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry; + +/** Operator of the FieldValues tab */ +public interface FieldValuesTabOperator extends ComponentOperatorRegistry.ComponentOperator { + void setFields(Collection fields); + + Set getFieldsToLoad(); +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/MLTPaneProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/MLTPaneProvider.java new file mode 100644 index 000000000000..9ad5b2ae1b72 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/MLTPaneProvider.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.fragments.search; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.JCheckBox; +import javax.swing.JFormattedTextField; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.event.TableModelEvent; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.standard.StandardAnalyzer; +import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry; +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.util.FontUtils; +import org.apache.lucene.luke.app.desktop.util.MessageUtils; +import org.apache.lucene.luke.app.desktop.util.TableUtils; +import org.apache.lucene.luke.models.search.MLTConfig; + +/** Provider of the MLT pane (tab) */ +public final class MLTPaneProvider implements Provider, MLTTabOperator { + + private final JLabel analyzerLbl = new JLabel(StandardAnalyzer.class.getName()); + + private final JFormattedTextField maxDocFreqFTF = new JFormattedTextField(); + + private final JFormattedTextField minDocFreqFTF = new JFormattedTextField(); + + private final JFormattedTextField minTermFreqFTF = new JFormattedTextField(); + + private final JCheckBox loadAllCB = new JCheckBox(); + + private final JTable fieldsTable = new JTable(); + + private final TabSwitcherProxy tabSwitcher; + + private final ListenerFunctions listeners = new ListenerFunctions(); + + private MLTConfig config = new MLTConfig.Builder().build(); + + @Inject + public MLTPaneProvider(TabSwitcherProxy tabSwitcher, + ComponentOperatorRegistry operatorRegistry) { + this.tabSwitcher = tabSwitcher; + + operatorRegistry.register(MLTTabOperator.class, this); + } + + @Override + public JScrollPane get() { + JPanel panel = new JPanel(); + panel.setOpaque(false); + panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); + panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); + + panel.add(initMltParamsPanel()); + panel.add(new JSeparator(JSeparator.HORIZONTAL)); + panel.add(initAnalyzerNamePanel()); + panel.add(new JSeparator(JSeparator.HORIZONTAL)); + panel.add(initFieldsSettingsPanel()); + + JScrollPane scrollPane = new JScrollPane(panel); + scrollPane.setOpaque(false); + scrollPane.getViewport().setOpaque(false); + return scrollPane; + } + + private JPanel initMltParamsPanel() { + JPanel panel = new JPanel(new GridLayout(3, 1)); + panel.setOpaque(false); + + JPanel maxDocFreq = new JPanel(new FlowLayout(FlowLayout.LEADING)); + maxDocFreq.setOpaque(false); + maxDocFreq.add(new JLabel(MessageUtils.getLocalizedMessage("search_mlt.label.max_doc_freq"))); + maxDocFreqFTF.setColumns(10); + maxDocFreqFTF.setValue(config.getMaxDocFreq()); + maxDocFreq.add(maxDocFreqFTF); + maxDocFreq.add(new JLabel(MessageUtils.getLocalizedMessage("label.int_required"))); + panel.add(maxDocFreq); + + JPanel minDocFreq = new JPanel(new FlowLayout(FlowLayout.LEADING)); + minDocFreq.setOpaque(false); + minDocFreq.add(new JLabel(MessageUtils.getLocalizedMessage("search_mlt.label.min_doc_freq"))); + minDocFreqFTF.setColumns(5); + minDocFreqFTF.setValue(config.getMinDocFreq()); + minDocFreq.add(minDocFreqFTF); + + minDocFreq.add(new JLabel(MessageUtils.getLocalizedMessage("label.int_required"))); + panel.add(minDocFreq); + + JPanel minTermFreq = new JPanel(new FlowLayout(FlowLayout.LEADING)); + minTermFreq.setOpaque(false); + minTermFreq.add(new JLabel(MessageUtils.getLocalizedMessage("serach_mlt.label.min_term_freq"))); + minTermFreqFTF.setColumns(5); + minTermFreqFTF.setValue(config.getMinTermFreq()); + minTermFreq.add(minTermFreqFTF); + minTermFreq.add(new JLabel(MessageUtils.getLocalizedMessage("label.int_required"))); + panel.add(minTermFreq); + + return panel; + } + + private JPanel initAnalyzerNamePanel() { + JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEADING)); + panel.setOpaque(false); + + panel.add(new JLabel(MessageUtils.getLocalizedMessage("search_mlt.label.analyzer"))); + + panel.add(analyzerLbl); + + JLabel changeLbl = new JLabel(MessageUtils.getLocalizedMessage("search_mlt.hyperlink.change")); + changeLbl.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + tabSwitcher.switchTab(TabbedPaneProvider.Tab.ANALYZER); + } + }); + panel.add(FontUtils.toLinkText(changeLbl)); + + return panel; + } + + private JPanel initFieldsSettingsPanel() { + JPanel panel = new JPanel(new BorderLayout()); + panel.setOpaque(false); + panel.setPreferredSize(new Dimension(500, 300)); + panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + JPanel header = new JPanel(new GridLayout(2, 1)); + header.setOpaque(false); + header.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); + header.add(new JLabel(MessageUtils.getLocalizedMessage("search_mlt.label.description"))); + loadAllCB.setText(MessageUtils.getLocalizedMessage("search_mlt.checkbox.select_all")); + loadAllCB.setSelected(true); + loadAllCB.setOpaque(false); + loadAllCB.addActionListener(listeners::loadAllFields); + header.add(loadAllCB); + panel.add(header, BorderLayout.PAGE_START); + + TableUtils.setupTable(fieldsTable, ListSelectionModel.SINGLE_SELECTION, new MLTFieldsTableModel(), null, MLTFieldsTableModel.Column.SELECT.getColumnWidth()); + fieldsTable.setPreferredScrollableViewportSize(fieldsTable.getPreferredSize()); + panel.add(new JScrollPane(fieldsTable), BorderLayout.CENTER); + + return panel; + } + + @Override + public void setAnalyzer(Analyzer analyzer) { + analyzerLbl.setText(analyzer.getClass().getName()); + } + + @Override + public void setFields(Collection fields) { + fieldsTable.setModel(new MLTFieldsTableModel(fields)); + fieldsTable.getColumnModel().getColumn(MLTFieldsTableModel.Column.SELECT.getIndex()).setMinWidth(MLTFieldsTableModel.Column.SELECT.getColumnWidth()); + fieldsTable.getColumnModel().getColumn(MLTFieldsTableModel.Column.SELECT.getIndex()).setMaxWidth(MLTFieldsTableModel.Column.SELECT.getColumnWidth()); + fieldsTable.getModel().addTableModelListener(listeners::tableDataChenged); + } + + @Override + public MLTConfig getConfig() { + List fields = new ArrayList<>(); + for (int row = 0; row < fieldsTable.getRowCount(); row++) { + boolean selected = (boolean) fieldsTable.getValueAt(row, MLTFieldsTableModel.Column.SELECT.getIndex()); + if (selected) { + fields.add((String) fieldsTable.getValueAt(row, MLTFieldsTableModel.Column.FIELD.getIndex())); + } + } + + return new MLTConfig.Builder() + .fields(fields) + .maxDocFreq((int) maxDocFreqFTF.getValue()) + .minDocFreq((int) minDocFreqFTF.getValue()) + .minTermFreq((int) minTermFreqFTF.getValue()) + .build(); + } + + private class ListenerFunctions { + + void loadAllFields(ActionEvent e) { + for (int i = 0; i < fieldsTable.getModel().getRowCount(); i++) { + if (loadAllCB.isSelected()) { + fieldsTable.setValueAt(true, i, MLTFieldsTableModel.Column.SELECT.getIndex()); + } else { + fieldsTable.setValueAt(false, i, MLTFieldsTableModel.Column.SELECT.getIndex()); + } + } + } + + void tableDataChenged(TableModelEvent e) { + int row = e.getFirstRow(); + int col = e.getColumn(); + if (col == MLTFieldsTableModel.Column.SELECT.getIndex()) { + boolean isLoad = (boolean) fieldsTable.getModel().getValueAt(row, col); + if (!isLoad) { + loadAllCB.setSelected(false); + } + } + } + } + +} + +final class MLTFieldsTableModel extends TableModelBase { + + enum Column implements TableColumnInfo { + SELECT("Select", 0, Boolean.class, 50), + FIELD("Field", 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; + } + } + + MLTFieldsTableModel() { + super(); + } + + MLTFieldsTableModel(Collection fields) { + super(fields.size()); + int i = 0; + for (String field : fields) { + data[i][Column.SELECT.getIndex()] = true; + data[i][Column.FIELD.getIndex()] = field; + i++; + } + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return columnIndex == Column.SELECT.getIndex(); + } + + @Override + public void setValueAt(Object value, int rowIndex, int columnIndex) { + data[rowIndex][columnIndex] = value; + fireTableCellUpdated(rowIndex, columnIndex); + } + + @Override + protected Column[] columnInfos() { + return Column.values(); + } +} \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/MLTTabOperator.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/MLTTabOperator.java new file mode 100644 index 000000000000..1180bc772d08 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/MLTTabOperator.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.fragments.search; + +import java.util.Collection; + +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry; +import org.apache.lucene.luke.models.search.MLTConfig; + +/** Operator of the MLT tab */ +public interface MLTTabOperator extends ComponentOperatorRegistry.ComponentOperator { + void setAnalyzer(Analyzer analyzer); + + void setFields(Collection fields); + + MLTConfig getConfig(); +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/QueryParserPaneProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/QueryParserPaneProvider.java new file mode 100644 index 000000000000..72da4500dc20 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/QueryParserPaneProvider.java @@ -0,0 +1,518 @@ +/* + * 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.fragments.search; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.DefaultCellEditor; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JFormattedTextField; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.ListSelectionModel; +import java.awt.Color; +import java.awt.FlowLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import org.apache.lucene.document.DateTools; +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.util.MessageUtils; +import org.apache.lucene.luke.app.desktop.util.TableUtils; +import org.apache.lucene.luke.models.search.QueryParserConfig; + +import static org.apache.lucene.luke.app.desktop.components.fragments.search.PointTypesTableModel.NumType.INT; + +/** Provider of the QueryParser pane (tab) */ +public final class QueryParserPaneProvider implements Provider, QueryParserTabOperator { + + private final JRadioButton standardRB = new JRadioButton(); + + private final JRadioButton classicRB = new JRadioButton(); + + private final JComboBox dfCB = new JComboBox<>(); + + private final JComboBox defOpCombo = new JComboBox<>(new String[]{QueryParserConfig.Operator.OR.name(), QueryParserConfig.Operator.AND.name()}); + + private final JCheckBox posIncCB = new JCheckBox(); + + private final JCheckBox wildCardCB = new JCheckBox(); + + private final JCheckBox splitWSCB = new JCheckBox(); + + private final JCheckBox genPhraseQueryCB = new JCheckBox(); + + private final JCheckBox genMultiTermSynonymsPhraseQueryCB = new JCheckBox(); + + private final JFormattedTextField slopFTF = new JFormattedTextField(); + + private final JFormattedTextField minSimFTF = new JFormattedTextField(); + + private final JFormattedTextField prefLenFTF = new JFormattedTextField(); + + private final JComboBox dateResCombo = new JComboBox<>(); + + private final JTextField locationTF = new JTextField(); + + private final JTextField timezoneTF = new JTextField(); + + private final JTable pointRangeQueryTable = new JTable(); + + private final ListenerFunctions listeners = new ListenerFunctions(); + + private final QueryParserConfig config = new QueryParserConfig.Builder().build(); + + @Inject + public QueryParserPaneProvider(ComponentOperatorRegistry operatorRegistry) { + operatorRegistry.register(QueryParserTabOperator.class, this); + } + + @Override + public JScrollPane get() { + JPanel panel = new JPanel(); + panel.setOpaque(false); + panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); + panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); + + panel.add(initSelectParserPane()); + panel.add(new JSeparator(JSeparator.HORIZONTAL)); + panel.add(initParserSettingsPanel()); + panel.add(new JSeparator(JSeparator.HORIZONTAL)); + panel.add(initPhraseQuerySettingsPanel()); + panel.add(new JSeparator(JSeparator.HORIZONTAL)); + panel.add(initFuzzyQuerySettingsPanel()); + panel.add(new JSeparator(JSeparator.HORIZONTAL)); + panel.add(initDateRangeQuerySettingsPanel()); + panel.add(new JSeparator(JSeparator.HORIZONTAL)); + panel.add(initPointRangeQuerySettingsPanel()); + + JScrollPane scrollPane = new JScrollPane(panel); + scrollPane.setOpaque(false); + scrollPane.getViewport().setOpaque(false); + return scrollPane; + } + + private JPanel initSelectParserPane() { + JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEADING)); + panel.setOpaque(false); + + standardRB.setText("StandardQueryParser"); + standardRB.setSelected(true); + standardRB.setOpaque(false); + standardRB.addActionListener(listeners::selectStandardQParser); + + classicRB.setText("Classic QueryParser"); + classicRB.setOpaque(false); + classicRB.addActionListener(listeners::selectClassicQparser); + + ButtonGroup group = new ButtonGroup(); + group.add(standardRB); + group.add(classicRB); + + panel.add(standardRB); + panel.add(classicRB); + + return panel; + } + + private JPanel initParserSettingsPanel() { + JPanel panel = new JPanel(new GridLayout(3, 2)); + panel.setOpaque(false); + + JPanel defField = new JPanel(new FlowLayout(FlowLayout.LEADING)); + defField.setOpaque(false); + JLabel dfLabel = new JLabel(MessageUtils.getLocalizedMessage("search_parser.label.df")); + defField.add(dfLabel); + defField.add(dfCB); + panel.add(defField); + + JPanel defOp = new JPanel(new FlowLayout(FlowLayout.LEADING)); + defOp.setOpaque(false); + JLabel defOpLabel = new JLabel(MessageUtils.getLocalizedMessage("search_parser.label.dop")); + defOp.add(defOpLabel); + defOpCombo.setSelectedItem(config.getDefaultOperator().name()); + defOp.add(defOpCombo); + panel.add(defOp); + + posIncCB.setText(MessageUtils.getLocalizedMessage("search_parser.checkbox.pos_incr")); + posIncCB.setSelected(config.isEnablePositionIncrements()); + posIncCB.setOpaque(false); + panel.add(posIncCB); + + wildCardCB.setText(MessageUtils.getLocalizedMessage("search_parser.checkbox.lead_wildcard")); + wildCardCB.setSelected(config.isAllowLeadingWildcard()); + wildCardCB.setOpaque(false); + panel.add(wildCardCB); + + splitWSCB.setText(MessageUtils.getLocalizedMessage("search_parser.checkbox.split_ws")); + splitWSCB.setEnabled(config.isSplitOnWhitespace()); + splitWSCB.addActionListener(listeners::toggleSplitOnWhiteSpace); + splitWSCB.setOpaque(false); + panel.add(splitWSCB); + + return panel; + } + + private JPanel initPhraseQuerySettingsPanel() { + JPanel panel = new JPanel(); + panel.setOpaque(false); + panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); + + JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING)); + header.setOpaque(false); + header.add(new JLabel(MessageUtils.getLocalizedMessage("search_parser.label.phrase_query"))); + panel.add(header); + + JPanel genPQ = new JPanel(new FlowLayout(FlowLayout.LEADING)); + genPQ.setOpaque(false); + genPQ.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 0)); + genPhraseQueryCB.setText(MessageUtils.getLocalizedMessage("search_parser.checkbox.gen_pq")); + genPhraseQueryCB.setEnabled(config.isAutoGeneratePhraseQueries()); + genPhraseQueryCB.setOpaque(false); + genPQ.add(genPhraseQueryCB); + panel.add(genPQ); + + JPanel genMTPQ = new JPanel(new FlowLayout(FlowLayout.LEADING)); + genMTPQ.setOpaque(false); + genMTPQ.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 0)); + genMultiTermSynonymsPhraseQueryCB.setText(MessageUtils.getLocalizedMessage("search_parser.checkbox.gen_mts")); + genMultiTermSynonymsPhraseQueryCB.setEnabled(config.isAutoGenerateMultiTermSynonymsPhraseQuery()); + genMultiTermSynonymsPhraseQueryCB.setOpaque(false); + genMTPQ.add(genMultiTermSynonymsPhraseQueryCB); + panel.add(genMTPQ); + + JPanel slop = new JPanel(new FlowLayout(FlowLayout.LEADING)); + slop.setOpaque(false); + slop.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 0)); + JLabel slopLabel = new JLabel(MessageUtils.getLocalizedMessage("search_parser.label.phrase_slop")); + slop.add(slopLabel); + slopFTF.setColumns(5); + slopFTF.setValue(config.getPhraseSlop()); + slop.add(slopFTF); + slop.add(new JLabel(MessageUtils.getLocalizedMessage("label.int_required"))); + panel.add(slop); + + return panel; + } + + private JPanel initFuzzyQuerySettingsPanel() { + JPanel panel = new JPanel(); + panel.setOpaque(false); + panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); + + JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING)); + header.setOpaque(false); + header.add(new JLabel(MessageUtils.getLocalizedMessage("search_parser.label.fuzzy_query"))); + panel.add(header); + + JPanel minSim = new JPanel(new FlowLayout(FlowLayout.LEADING)); + minSim.setOpaque(false); + minSim.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 0)); + JLabel minSimLabel = new JLabel(MessageUtils.getLocalizedMessage("search_parser.label.fuzzy_minsim")); + minSim.add(minSimLabel); + minSimFTF.setColumns(5); + minSimFTF.setValue(config.getFuzzyMinSim()); + minSim.add(minSimFTF); + minSim.add(new JLabel(MessageUtils.getLocalizedMessage("label.float_required"))); + panel.add(minSim); + + JPanel prefLen = new JPanel(new FlowLayout(FlowLayout.LEADING)); + prefLen.setOpaque(false); + prefLen.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 0)); + JLabel prefLenLabel = new JLabel(MessageUtils.getLocalizedMessage("search_parser.label.fuzzy_preflen")); + prefLen.add(prefLenLabel); + prefLenFTF.setColumns(5); + prefLenFTF.setValue(config.getFuzzyPrefixLength()); + prefLen.add(prefLenFTF); + prefLen.add(new JLabel(MessageUtils.getLocalizedMessage("label.int_required"))); + panel.add(prefLen); + + return panel; + } + + private JPanel initDateRangeQuerySettingsPanel() { + JPanel panel = new JPanel(); + panel.setOpaque(false); + panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); + + JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING)); + header.setOpaque(false); + header.add(new JLabel(MessageUtils.getLocalizedMessage("search_parser.label.daterange_query"))); + panel.add(header); + + JPanel resolution = new JPanel(new FlowLayout(FlowLayout.LEADING)); + resolution.setOpaque(false); + resolution.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 0)); + JLabel resLabel = new JLabel(MessageUtils.getLocalizedMessage("search_parser.label.date_res")); + resolution.add(resLabel); + Arrays.stream(DateTools.Resolution.values()).map(DateTools.Resolution::name).forEach(dateResCombo::addItem); + dateResCombo.setSelectedItem(config.getDateResolution().name()); + resolution.add(dateResCombo); + panel.add(resolution); + + JPanel locale = new JPanel(new FlowLayout(FlowLayout.LEADING)); + locale.setOpaque(false); + locale.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 0)); + JLabel locLabel = new JLabel(MessageUtils.getLocalizedMessage("search_parser.label.locale")); + locale.add(locLabel); + locationTF.setColumns(10); + locationTF.setText(config.getLocale().toLanguageTag()); + locale.add(locationTF); + JLabel tzLabel = new JLabel(MessageUtils.getLocalizedMessage("search_parser.label.timezone")); + locale.add(tzLabel); + timezoneTF.setColumns(10); + timezoneTF.setText(config.getTimeZone().getID()); + locale.add(timezoneTF); + panel.add(locale); + + return panel; + } + + private JPanel initPointRangeQuerySettingsPanel() { + JPanel panel = new JPanel(); + panel.setOpaque(false); + panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); + panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + + JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING)); + header.setOpaque(false); + header.add(new JLabel(MessageUtils.getLocalizedMessage("search_parser.label.pointrange_query"))); + panel.add(header); + + JPanel headerNote = new JPanel(new FlowLayout(FlowLayout.LEADING)); + headerNote.setOpaque(false); + headerNote.add(new JLabel(MessageUtils.getLocalizedMessage("search_parser.label.pointrange_hint"))); + panel.add(headerNote); + + TableUtils.setupTable(pointRangeQueryTable, ListSelectionModel.SINGLE_SELECTION, new PointTypesTableModel(), null, PointTypesTableModel.Column.FIELD.getColumnWidth()); + pointRangeQueryTable.setShowGrid(true); + JScrollPane scrollPane = new JScrollPane(pointRangeQueryTable); + panel.add(scrollPane); + + return panel; + } + + @Override + public void setSearchableFields(Collection searchableFields) { + for (String field : searchableFields) { + dfCB.addItem(field); + } + } + + @Override + public void setRangeSearchableFields(Collection rangeSearchableFields) { + pointRangeQueryTable.setModel(new PointTypesTableModel(rangeSearchableFields)); + pointRangeQueryTable.setShowGrid(true); + String[] numTypes = Arrays.stream(PointTypesTableModel.NumType.values()) + .map(PointTypesTableModel.NumType::name) + .toArray(String[]::new); + JComboBox numTypesCombo = new JComboBox<>(numTypes); + numTypesCombo.setRenderer((list, value, index, isSelected, cellHasFocus) -> new JLabel(value)); + pointRangeQueryTable.getColumnModel().getColumn(PointTypesTableModel.Column.TYPE.getIndex()).setCellEditor(new DefaultCellEditor(numTypesCombo)); + pointRangeQueryTable.getColumnModel().getColumn(PointTypesTableModel.Column.TYPE.getIndex()).setCellRenderer( + (table, value, isSelected, hasFocus, row, column) -> new JLabel((String) value) + ); + pointRangeQueryTable.getColumnModel().getColumn(PointTypesTableModel.Column.FIELD.getIndex()).setPreferredWidth(PointTypesTableModel.Column.FIELD.getColumnWidth()); + pointRangeQueryTable.setPreferredScrollableViewportSize(pointRangeQueryTable.getPreferredSize()); + + // set default type to Integer + for (int i = 0; i < rangeSearchableFields.size(); i++) { + pointRangeQueryTable.setValueAt(INT.name(), i, PointTypesTableModel.Column.TYPE.getIndex()); + } + + } + + @Override + public QueryParserConfig getConfig() { + int phraseSlop = (int) slopFTF.getValue(); + float fuzzyMinSimFloat = (float) minSimFTF.getValue(); + int fuzzyPrefLenInt = (int) prefLenFTF.getValue(); + + Map> typeMap = new HashMap<>(); + for (int row = 0; row < pointRangeQueryTable.getModel().getRowCount(); row++) { + String field = (String) pointRangeQueryTable.getValueAt(row, PointTypesTableModel.Column.FIELD.getIndex()); + String type = (String) pointRangeQueryTable.getValueAt(row, PointTypesTableModel.Column.TYPE.getIndex()); + switch (PointTypesTableModel.NumType.valueOf(type)) { + case INT: + typeMap.put(field, Integer.class); + break; + case LONG: + typeMap.put(field, Long.class); + break; + case FLOAT: + typeMap.put(field, Float.class); + break; + case DOUBLE: + typeMap.put(field, Double.class); + break; + default: + break; + } + } + + return new QueryParserConfig.Builder() + .useClassicParser(classicRB.isSelected()) + .defaultOperator(QueryParserConfig.Operator.valueOf((String) defOpCombo.getSelectedItem())) + .enablePositionIncrements(posIncCB.isSelected()) + .allowLeadingWildcard(wildCardCB.isSelected()) + .splitOnWhitespace(splitWSCB.isSelected()) + .autoGeneratePhraseQueries(genPhraseQueryCB.isSelected()) + .autoGenerateMultiTermSynonymsPhraseQuery(genMultiTermSynonymsPhraseQueryCB.isSelected()) + .phraseSlop(phraseSlop) + .fuzzyMinSim(fuzzyMinSimFloat) + .fuzzyPrefixLength(fuzzyPrefLenInt) + .dateResolution(DateTools.Resolution.valueOf((String) dateResCombo.getSelectedItem())) + .locale(new Locale(locationTF.getText())) + .timeZone(TimeZone.getTimeZone(timezoneTF.getText())) + .typeMap(typeMap) + .build(); + } + + @Override + public String getDefaultField() { + return (String) dfCB.getSelectedItem(); + } + + private class ListenerFunctions { + + void selectStandardQParser(ActionEvent e) { + splitWSCB.setEnabled(false); + genPhraseQueryCB.setEnabled(false); + genMultiTermSynonymsPhraseQueryCB.setEnabled(false); + TableUtils.setEnabled(pointRangeQueryTable, true); + } + + void selectClassicQparser(ActionEvent e) { + splitWSCB.setEnabled(true); + if (splitWSCB.isSelected()) { + genPhraseQueryCB.setEnabled(true); + } else { + genPhraseQueryCB.setEnabled(false); + genPhraseQueryCB.setSelected(false); + } + genMultiTermSynonymsPhraseQueryCB.setEnabled(true); + pointRangeQueryTable.setEnabled(false); + pointRangeQueryTable.setForeground(Color.gray); + TableUtils.setEnabled(pointRangeQueryTable, false); + } + + void toggleSplitOnWhiteSpace(ActionEvent e) { + if (splitWSCB.isSelected()) { + genPhraseQueryCB.setEnabled(true); + } else { + genPhraseQueryCB.setEnabled(false); + genPhraseQueryCB.setSelected(false); + } + } + + } + +} + +final class PointTypesTableModel extends TableModelBase { + + enum Column implements TableColumnInfo { + + FIELD("Field", 0, String.class, 300), + TYPE("Numeric Type", 1, NumType.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; + } + } + + enum NumType { + + INT, LONG, FLOAT, DOUBLE + + } + + PointTypesTableModel() { + super(); + } + + PointTypesTableModel(Collection rangeSearchableFields) { + super(rangeSearchableFields.size()); + int i = 0; + for (String field : rangeSearchableFields) { + data[i++][Column.FIELD.getIndex()] = field; + } + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return columnIndex == Column.TYPE.getIndex(); + } + + @Override + public void setValueAt(Object value, int rowIndex, int columnIndex) { + data[rowIndex][columnIndex] = value; + fireTableCellUpdated(rowIndex, columnIndex); + } + + @Override + protected Column[] columnInfos() { + return Column.values(); + } +} + diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/QueryParserTabOperator.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/QueryParserTabOperator.java new file mode 100644 index 000000000000..4ef9855deece --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/QueryParserTabOperator.java @@ -0,0 +1,35 @@ +/* + * 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.fragments.search; + +import java.util.Collection; + +import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry; +import org.apache.lucene.luke.models.search.QueryParserConfig; + +/** Operator of the QueryParser tab */ +public interface QueryParserTabOperator extends ComponentOperatorRegistry.ComponentOperator { + void setSearchableFields(Collection searchableFields); + + void setRangeSearchableFields(Collection rangeSearchableFields); + + QueryParserConfig getConfig(); + + String getDefaultField(); +} + diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/SimilarityPaneProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/SimilarityPaneProvider.java new file mode 100644 index 000000000000..0fb48e99f08c --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/SimilarityPaneProvider.java @@ -0,0 +1,149 @@ +/* + * 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.fragments.search; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.JCheckBox; +import javax.swing.JFormattedTextField; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry; +import org.apache.lucene.luke.app.desktop.util.MessageUtils; +import org.apache.lucene.luke.app.desktop.util.StyleConstants; +import org.apache.lucene.luke.models.search.SimilarityConfig; + +/** Provider of the Similarity pane (tab) */ +public final class SimilarityPaneProvider implements Provider, SimilarityTabOperator { + + private final JCheckBox tfidfCB = new JCheckBox(); + + private final JCheckBox discardOverlapsCB = new JCheckBox(); + + private final JFormattedTextField k1FTF = new JFormattedTextField(); + + private final JFormattedTextField bFTF = new JFormattedTextField(); + + private final SimilarityConfig config = new SimilarityConfig.Builder().build(); + + private final ListenerFunctions listeners = new ListenerFunctions(); + + @Inject + public SimilarityPaneProvider(ComponentOperatorRegistry operatorRegistry) { + operatorRegistry.register(SimilarityTabOperator.class, this); + } + + @Override + public JScrollPane get() { + JPanel panel = new JPanel(); + panel.setOpaque(false); + panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); + panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + panel.add(initSimilaritySettingsPanel()); + + JScrollPane scrollPane = new JScrollPane(panel); + scrollPane.setOpaque(false); + scrollPane.getViewport().setOpaque(false); + return scrollPane; + } + + private JPanel initSimilaritySettingsPanel() { + JPanel panel = new JPanel(new GridLayout(4, 1)); + panel.setOpaque(false); + panel.setMaximumSize(new Dimension(700, 220)); + + tfidfCB.setText(MessageUtils.getLocalizedMessage("search_similarity.checkbox.use_classic")); + tfidfCB.setOpaque(false); + tfidfCB.addActionListener(listeners::toggleTfIdf); + panel.add(tfidfCB); + + discardOverlapsCB.setText(MessageUtils.getLocalizedMessage("search_similarity.checkbox.discount_overlaps")); + discardOverlapsCB.setSelected(config.isUseClassicSimilarity()); + discardOverlapsCB.setOpaque(false); + panel.add(discardOverlapsCB); + + JLabel bm25Label = new JLabel(MessageUtils.getLocalizedMessage("search_similarity.label.bm25_params")); + panel.add(bm25Label); + + JPanel bm25Params = new JPanel(new FlowLayout(FlowLayout.LEADING)); + bm25Params.setOpaque(false); + bm25Params.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 0)); + + JPanel k1Val = new JPanel(new FlowLayout(FlowLayout.LEADING)); + k1Val.setOpaque(false); + k1Val.add(new JLabel("k1: ")); + k1FTF.setColumns(5); + k1FTF.setValue(config.getK1()); + k1Val.add(k1FTF); + k1Val.add(new JLabel(MessageUtils.getLocalizedMessage("label.float_required"))); + bm25Params.add(k1Val); + + JPanel bVal = new JPanel(new FlowLayout(FlowLayout.LEADING)); + bVal.setOpaque(false); + bVal.add(new JLabel("b: ")); + bFTF.setColumns(5); + bFTF.setValue(config.getB()); + bVal.add(bFTF); + bVal.add(new JLabel(MessageUtils.getLocalizedMessage("label.float_required"))); + bm25Params.add(bVal); + + panel.add(bm25Params); + + return panel; + } + + @Override + public SimilarityConfig getConfig() { + float k1 = (float) k1FTF.getValue(); + float b = (float) bFTF.getValue(); + return new SimilarityConfig.Builder() + .useClassicSimilarity(tfidfCB.isSelected()) + .discountOverlaps(discardOverlapsCB.isSelected()) + .k1(k1) + .b(b) + .build(); + } + + private class ListenerFunctions { + + void toggleTfIdf(ActionEvent e) { + if (tfidfCB.isSelected()) { + k1FTF.setEnabled(false); + k1FTF.setBackground(StyleConstants.DISABLED_COLOR); + bFTF.setEnabled(false); + bFTF.setBackground(StyleConstants.DISABLED_COLOR); + } else { + k1FTF.setEnabled(true); + k1FTF.setBackground(Color.white); + bFTF.setEnabled(true); + bFTF.setBackground(Color.white); + } + } + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/SimilarityTabOperator.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/SimilarityTabOperator.java new file mode 100644 index 000000000000..290a371c3299 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/SimilarityTabOperator.java @@ -0,0 +1,26 @@ +/* + * 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.fragments.search; + +import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry; +import org.apache.lucene.luke.models.search.SimilarityConfig; + +/** Operator of the Similarity tab */ +public interface SimilarityTabOperator extends ComponentOperatorRegistry.ComponentOperator { + SimilarityConfig getConfig(); +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/SortPaneProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/SortPaneProvider.java new file mode 100644 index 000000000000..baedeb7e4c18 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/SortPaneProvider.java @@ -0,0 +1,238 @@ +/* + * 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.fragments.search; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import com.google.common.base.Strings; +import com.google.inject.Inject; +import com.google.inject.Provider; +import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry; +import org.apache.lucene.luke.app.desktop.util.MessageUtils; +import org.apache.lucene.luke.models.search.Search; +import org.apache.lucene.search.Sort; +import org.apache.lucene.search.SortField; +import org.apache.lucene.search.SortedNumericSortField; + +/** Provider of the Sort pane (tab) */ +public final class SortPaneProvider implements Provider, SortTabOperator { + + private static final String COMMAND_FIELD_COMBO1 = "fieldCombo1"; + + private static final String COMMAND_FIELD_COMBO2 = "fieldCombo2"; + + private final JComboBox fieldCombo1 = new JComboBox<>(); + + private final JComboBox typeCombo1 = new JComboBox<>(); + + private final JComboBox orderCombo1 = new JComboBox<>(Order.names()); + + private final JComboBox fieldCombo2 = new JComboBox<>(); + + private final JComboBox typeCombo2 = new JComboBox<>(); + + private final JComboBox orderCombo2 = new JComboBox<>(Order.names()); + + private final ListenerFunctions listeners = new ListenerFunctions(); + + private Search searchModel; + + @Inject + public SortPaneProvider(ComponentOperatorRegistry operatorRegistry) { + operatorRegistry.register(SortTabOperator.class, this); + } + + @Override + public JScrollPane get() { + JPanel panel = new JPanel(new GridLayout(1, 1)); + panel.setOpaque(false); + panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + + panel.add(initSortConfigsPanel()); + + JScrollPane scrollPane = new JScrollPane(panel); + scrollPane.setOpaque(false); + scrollPane.getViewport().setOpaque(false); + return scrollPane; + } + + private JPanel initSortConfigsPanel() { + JPanel panel = new JPanel(new GridLayout(5, 1)); + panel.setOpaque(false); + panel.setMaximumSize(new Dimension(500, 200)); + + panel.add(new JLabel(MessageUtils.getLocalizedMessage("search_sort.label.primary"))); + + JPanel primary = new JPanel(new FlowLayout(FlowLayout.LEADING)); + primary.setOpaque(false); + primary.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0)); + primary.add(new JLabel(MessageUtils.getLocalizedMessage("search_sort.label.field"))); + fieldCombo1.setPreferredSize(new Dimension(150, 30)); + fieldCombo1.setActionCommand(COMMAND_FIELD_COMBO1); + fieldCombo1.addActionListener(listeners::changeField); + primary.add(fieldCombo1); + primary.add(new JLabel(MessageUtils.getLocalizedMessage("search_sort.label.type"))); + typeCombo1.setPreferredSize(new Dimension(130, 30)); + typeCombo1.addItem(""); + typeCombo1.setEnabled(false); + primary.add(typeCombo1); + primary.add(new JLabel(MessageUtils.getLocalizedMessage("search_sort.label.order"))); + orderCombo1.setPreferredSize(new Dimension(100, 30)); + orderCombo1.setEnabled(false); + primary.add(orderCombo1); + panel.add(primary); + + panel.add(new JLabel(MessageUtils.getLocalizedMessage("search_sort.label.secondary"))); + + JPanel secondary = new JPanel(new FlowLayout(FlowLayout.LEADING)); + secondary.setOpaque(false); + secondary.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0)); + secondary.add(new JLabel(MessageUtils.getLocalizedMessage("search_sort.label.field"))); + fieldCombo2.setPreferredSize(new Dimension(150, 30)); + fieldCombo2.setActionCommand(COMMAND_FIELD_COMBO2); + fieldCombo2.addActionListener(listeners::changeField); + secondary.add(fieldCombo2); + secondary.add(new JLabel(MessageUtils.getLocalizedMessage("search_sort.label.type"))); + typeCombo2.setPreferredSize(new Dimension(130, 30)); + typeCombo2.addItem(""); + typeCombo2.setEnabled(false); + secondary.add(typeCombo2); + secondary.add(new JLabel(MessageUtils.getLocalizedMessage("search_sort.label.order"))); + orderCombo2.setPreferredSize(new Dimension(100, 30)); + orderCombo2.setEnabled(false); + secondary.add(orderCombo2); + panel.add(secondary); + + JPanel clear = new JPanel(new FlowLayout(FlowLayout.LEADING)); + clear.setOpaque(false); + JButton clearBtn = new JButton(MessageUtils.getLocalizedMessage("search_sort.button.clear")); + clearBtn.addActionListener(listeners::clear); + clear.add(clearBtn); + panel.add(clear); + + return panel; + } + + @Override + public void setSearchModel(Search model) { + searchModel = model; + } + + @Override + public void setSortableFields(Collection sortableFields) { + fieldCombo1.removeAllItems(); + fieldCombo2.removeAllItems(); + + fieldCombo1.addItem(""); + fieldCombo2.addItem(""); + + for (String field : sortableFields) { + fieldCombo1.addItem(field); + fieldCombo2.addItem(field); + } + } + + @Override + public Sort getSort() { + if (Strings.isNullOrEmpty((String) fieldCombo1.getSelectedItem()) && Strings.isNullOrEmpty((String) fieldCombo2.getSelectedItem())) { + return null; + } + + List li = new ArrayList<>(); + if (!Strings.isNullOrEmpty((String) fieldCombo1.getSelectedItem())) { + searchModel.getSortType((String) fieldCombo1.getSelectedItem(), (String) typeCombo1.getSelectedItem(), isReverse(orderCombo1)).ifPresent(li::add); + } + if (!Strings.isNullOrEmpty((String) fieldCombo2.getSelectedItem())) { + searchModel.getSortType((String) fieldCombo2.getSelectedItem(), (String) typeCombo2.getSelectedItem(), isReverse(orderCombo2)).ifPresent(li::add); + } + return new Sort(li.toArray(new SortField[0])); + } + + private boolean isReverse(JComboBox order) { + return Order.valueOf((String) order.getSelectedItem()) == Order.DESC; + } + + private class ListenerFunctions { + + void changeField(ActionEvent e) { + if (e.getActionCommand().equalsIgnoreCase(COMMAND_FIELD_COMBO1)) { + resetField(fieldCombo1, typeCombo1, orderCombo1); + } else if (e.getActionCommand().equalsIgnoreCase(COMMAND_FIELD_COMBO2)) { + resetField(fieldCombo2, typeCombo2, orderCombo2); + } + } + + private void resetField(JComboBox fieldCombo, JComboBox typeCombo, JComboBox orderCombo) { + typeCombo.removeAllItems(); + if (Strings.isNullOrEmpty((String) fieldCombo.getSelectedItem())) { + typeCombo.addItem(""); + typeCombo.setEnabled(false); + orderCombo.setEnabled(false); + } else { + List sortFields = searchModel.guessSortTypes((String) fieldCombo.getSelectedItem()); + sortFields.stream() + .map(sf -> { + if (sf instanceof SortedNumericSortField) { + return ((SortedNumericSortField) sf).getNumericType().name(); + } else { + return sf.getType().name(); + } + }).forEach(typeCombo::addItem); + typeCombo.setEnabled(true); + orderCombo.setEnabled(true); + } + } + + void clear(ActionEvent e) { + fieldCombo1.setSelectedIndex(0); + typeCombo1.removeAllItems(); + typeCombo1.setSelectedItem(""); + typeCombo1.setEnabled(false); + orderCombo1.setSelectedIndex(0); + orderCombo1.setEnabled(false); + + fieldCombo2.setSelectedIndex(0); + typeCombo2.removeAllItems(); + typeCombo2.setSelectedItem(""); + typeCombo2.setEnabled(false); + orderCombo2.setSelectedIndex(0); + orderCombo2.setEnabled(false); + } + } + + enum Order { + ASC, DESC; + + static String[] names() { + return Arrays.stream(values()).map(Order::name).toArray(String[]::new); + } + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/SortTabOperator.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/SortTabOperator.java new file mode 100644 index 000000000000..4cf43d11e244 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/SortTabOperator.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.components.fragments.search; + +import java.util.Collection; + +import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry; +import org.apache.lucene.luke.models.search.Search; +import org.apache.lucene.search.Sort; + +/** Operator of the Sort tab */ +public interface SortTabOperator extends ComponentOperatorRegistry.ComponentOperator { + void setSearchModel(Search model); + + void setSortableFields(Collection sortableFields); + + Sort getSort(); +} + diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/package-info.java new file mode 100644 index 000000000000..85bf96f8c7ad --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/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. + */ + +/** UI parts embedded in the Search tab */ +package org.apache.lucene.luke.app.desktop.components.fragments.search; \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/package-info.java new file mode 100644 index 000000000000..fefd0c889250 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/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. + */ + +/** UI components of the desktop Luke */ +package org.apache.lucene.luke.app.desktop.components; \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/dto/documents/NewField.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/dto/documents/NewField.java new file mode 100644 index 000000000000..a215cc074e36 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/dto/documents/NewField.java @@ -0,0 +1,139 @@ +/* + * 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.dto.documents; + +import javax.annotation.Nonnull; + +import org.apache.lucene.document.DoublePoint; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.FieldType; +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.IndexableFieldType; +import org.apache.lucene.luke.app.desktop.util.NumericUtils; + +/** Data holder for a new field. This is used in the add document dialog. */ +public final class NewField { + + private boolean deleted; + + private String name; + + private Class type; + + private String value; + + private IndexableFieldType fieldType; + + private boolean stored; + + public static NewField newInstance() { + NewField f = new NewField(); + f.deleted = false; + f.name = ""; + f.type = TextField.class; + f.value = ""; + f.fieldType = new TextField("", "", Field.Store.NO).fieldType(); + f.stored = f.fieldType.stored(); + return f; + } + + private NewField() { + } + + public boolean isDeleted() { + return deleted; + } + + public void setDeleted(boolean value) { + deleted = value; + } + + public String getName() { + return name; + } + + public void setName(@Nonnull String name) { + this.name = name; + } + + public Class getType() { + return type; + } + + public void setType(@Nonnull Class type) { + this.type = type; + } + + public void resetFieldType(Class type) { + if (type.equals(TextField.class)) { + fieldType = new TextField("", "", Field.Store.NO).fieldType(); + } else if (type.equals(StringField.class)) { + fieldType = new StringField("", "", Field.Store.NO).fieldType(); + } else if (type.equals(IntPoint.class)) { + fieldType = new IntPoint("", NumericUtils.convertToIntArray(value, true)).fieldType(); + } else if (type.equals(LongPoint.class)) { + fieldType = new LongPoint("", NumericUtils.convertToLongArray(value, true)).fieldType(); + } else if (type.equals(FloatPoint.class)) { + fieldType = new FloatPoint("", NumericUtils.convertToFloatArray(value, true)).fieldType(); + } else if (type.equals(DoublePoint.class)) { + fieldType = new DoublePoint("", NumericUtils.convertToDoubleArray(value, true)).fieldType(); + } else if (type.equals(SortedDocValuesField.class)) { + fieldType = new SortedDocValuesField("", null).fieldType(); + } else if (type.equals(SortedSetDocValuesField.class)) { + fieldType = new SortedSetDocValuesField("", null).fieldType(); + } else if (type.equals(NumericDocValuesField.class)) { + fieldType = new NumericDocValuesField("", 0).fieldType(); + } else if (type.equals(SortedNumericDocValuesField.class)) { + fieldType = new SortedNumericDocValuesField("", 0).fieldType(); + } else if (type.equals(StoredField.class)) { + fieldType = new StoredField("", "").fieldType(); + } else if (type.equals(Field.class)) { + fieldType = new FieldType(this.fieldType); + } + } + + public IndexableFieldType getFieldType() { + return fieldType; + } + + public boolean isStored() { + return stored; + } + + public void setStored(boolean stored) { + this.stored = stored; + } + + public String getValue() { + return value; + } + + public void setValue(@Nonnull String value) { + this.value = value; + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/dto/documents/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/dto/documents/package-info.java new file mode 100644 index 000000000000..0f08238ddd1b --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/dto/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. + */ + +/** DTO classes */ +package org.apache.lucene.luke.app.desktop.dto.documents; \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/package-info.java new file mode 100644 index 000000000000..908b23454402 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/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. + */ + +/** Desktop Luke application */ +package org.apache.lucene.luke.app.desktop; \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/DialogOpener.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/DialogOpener.java new file mode 100644 index 000000000000..bc633e3c5e37 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/DialogOpener.java @@ -0,0 +1,52 @@ +/* + * 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.util; + +import javax.swing.JDialog; +import java.awt.Window; +import java.util.function.Consumer; + +import org.apache.lucene.luke.app.desktop.LukeMain; + +/** An utility class for opening a dialog */ +public class DialogOpener { + + private final T factory; + + public DialogOpener(T factory) { + this.factory = factory; + } + + public void open(String title, int width, int height, Consumer initializer, + String... styleSheets) { + open(LukeMain.getOwnerFrame(), title, width, height, initializer, styleSheets); + } + + public void open(Window owner, String title, int width, int height, Consumer initializer, + String... styleSheets) { + initializer.accept(factory); + JDialog dialog = factory.create(owner, title, width, height); + dialog.setVisible(true); + } + + /** Base interface of factory of the dialogs */ + public interface DialogFactory { + JDialog create(Window owner, String title, int width, int height); + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/ExceptionHandler.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/ExceptionHandler.java new file mode 100644 index 000000000000..da43c63849c6 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/ExceptionHandler.java @@ -0,0 +1,44 @@ +/* + * 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.util; + +import java.lang.invoke.MethodHandles; + +import org.apache.lucene.luke.app.desktop.MessageBroker; +import org.apache.lucene.luke.models.LukeException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** An utility class for handling exception */ +public final class ExceptionHandler { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + public static void handle(Throwable t, MessageBroker messageBroker) { + if (t instanceof LukeException) { + Throwable cause = t.getCause(); + String message = (cause == null) ? t.getMessage() : t.getMessage() + " " + cause.getMessage(); + log.warn(t.getMessage(), t); + messageBroker.showStatusMessage(message); + } else { + log.error(t.getMessage(), t); + messageBroker.showUnknownErrorMessage(); + } + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/FontUtils.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/FontUtils.java new file mode 100644 index 000000000000..37268d16f64d --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/FontUtils.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.util; + +import javax.swing.JLabel; +import java.awt.Font; +import java.awt.FontFormatException; +import java.awt.font.TextAttribute; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +/** Font utilities */ +public class FontUtils { + + @SuppressWarnings("unchecked") + public static JLabel toLinkText(JLabel label) { + label.setForeground(StyleConstants.LINK_COLOR); + Font font = label.getFont(); + Map attributes = (Map) font.getAttributes(); + attributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); + label.setFont(font.deriveFont(attributes)); + return label; + } + + public static Font createElegantIconFont() throws IOException, FontFormatException { + InputStream is = FontUtils.class.getClassLoader().getResourceAsStream("font/ElegantIcons.ttf"); + return Font.createFont(Font.TRUETYPE_FONT, is); + } + + /** + * Generates HTML text with embedded Elegant Icon Font. + * See: https://www.elegantthemes.com/blog/resources/elegant-icon-font + * + * @param iconRef HTML numeric character reference of the icon + */ + public static String elegantIconHtml(String iconRef) { + return "" + iconRef + ""; + } + + /** + * Generates HTML text with embedded Elegant Icon Font. + * + * @param iconRef HTML numeric character reference of the icon + * @param text description + */ + public static String elegantIconHtml(String iconRef, String text) { + return "" + iconRef + " " + text + ""; + } + + private FontUtils() { + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/HelpHeaderRenderer.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/HelpHeaderRenderer.java new file mode 100644 index 000000000000..41c7f079e507 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/HelpHeaderRenderer.java @@ -0,0 +1,129 @@ +/* + * 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.util; + +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.UIManager; +import javax.swing.table.JTableHeader; +import javax.swing.table.TableCellRenderer; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.Objects; + +import org.apache.lucene.luke.app.desktop.components.dialog.HelpDialogFactory; + +/** + * Cell render class for table header with help dialog. + */ +public final class HelpHeaderRenderer implements TableCellRenderer { + + private JTable table; + + private final JPanel panel = new JPanel(); + + private final JComponent helpContent; + + private final HelpDialogFactory helpDialogFactory; + + private final String title; + + private final String desc; + + private final JDialog parent; + + public HelpHeaderRenderer(String title, String desc, JComponent helpContent, HelpDialogFactory helpDialogFactory) { + this(title, desc, helpContent, helpDialogFactory, null); + } + + public HelpHeaderRenderer(String title, String desc, JComponent helpContent, HelpDialogFactory helpDialogFactory, + JDialog parent) { + this.title = title; + this.desc = desc; + this.helpContent = helpContent; + this.helpDialogFactory = helpDialogFactory; + this.parent = parent; + } + + @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.LEADING)); + panel.setBorder(UIManager.getBorder("TableHeader.cellBorder")); + panel.add(new JLabel(value.toString())); + + // add label with mouse click listener + // when the label is clicked, help dialog will be displayed. + JLabel helpLabel = new JLabel(FontUtils.elegantIconHtml("t", MessageUtils.getLocalizedMessage("label.help"))); + helpLabel.setHorizontalAlignment(JLabel.LEFT); + helpLabel.setIconTextGap(5); + panel.add(FontUtils.toLinkText(helpLabel)); + + // add mouse listener to JTableHeader object. + // see: https://stackoverflow.com/questions/7137786/how-can-i-put-a-control-in-the-jtableheader-of-a-jtable + header.addMouseListener(new HelpClickListener(column)); + } + } + return panel; + } + + class HelpClickListener extends MouseAdapter { + + int column; + + HelpClickListener(int column) { + this.column = column; + } + + @Override + public void mouseClicked(MouseEvent e) { + showPopupIfNeeded(e); + } + + private void showPopupIfNeeded(MouseEvent e) { + JTableHeader header = (JTableHeader) e.getSource(); + int column = header.getTable().columnAtPoint(e.getPoint()); + if (column == this.column && e.getClickCount() == 1 && column != -1) { + // only when the targeted column header is clicked, pop up the dialog + if (Objects.nonNull(parent)) { + new DialogOpener<>(helpDialogFactory).open(parent, title, 600, 350, + (factory) -> { + factory.setDesc(desc); + factory.setContent(helpContent); + }); + } else { + new DialogOpener<>(helpDialogFactory).open(title, 600, 350, + (factory) -> { + factory.setDesc(desc); + factory.setContent(helpContent); + }); + } + } + } + + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/ImageUtils.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/ImageUtils.java new file mode 100644 index 000000000000..d9f16c31082c --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/ImageUtils.java @@ -0,0 +1,43 @@ +/* + * 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.util; + +import javax.swing.ImageIcon; +import java.awt.Image; + +/** Image utilities */ +public class ImageUtils { + + public static ImageIcon createImageIcon(String path, int width, int height) { + return createImageIcon(path, "", width, height); + } + + public static ImageIcon createImageIcon(String path, String description, int width, int height) { + java.net.URL imgURL = ImageUtils.class.getResource(path); + if (imgURL != null) { + ImageIcon originalIcon = new ImageIcon(imgURL, description); + ImageIcon icon = new ImageIcon(originalIcon.getImage().getScaledInstance(width, height, Image.SCALE_DEFAULT)); + return icon; + } else { + return null; + } + } + + private ImageUtils() { + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/ListUtils.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/ListUtils.java new file mode 100644 index 000000000000..9d777fb3495d --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/ListUtils.java @@ -0,0 +1,43 @@ +/* + * 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.util; + +import javax.swing.JList; +import javax.swing.ListModel; +import java.util.List; +import java.util.function.IntFunction; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** List model utilities */ +public class ListUtils { + + public static List getAllItems(JList jlist) { + ListModel model = jlist.getModel(); + return getAllItems(jlist, model::getElementAt); + } + + public static List getAllItems(JList jlist, IntFunction mapFunc) { + ListModel model = jlist.getModel(); + return IntStream.range(0, model.getSize()).mapToObj(mapFunc).collect(Collectors.toList()); + } + + private ListUtils() { + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/MessageUtils.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/MessageUtils.java new file mode 100644 index 000000000000..ca80a6ce92c5 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/MessageUtils.java @@ -0,0 +1,59 @@ +/* + * 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.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.util.Locale; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; + +/** + * Utilities for accessing message resources. + */ +public class MessageUtils { + + public static String getLocalizedMessage(String key) { + return bundle.getString(key); + } + + public static String getLocalizedMessage(String key, Object... args) { + String pattern = bundle.getString(key); + return new MessageFormat(pattern, Locale.ENGLISH).format(args); + } + + // https://stackoverflow.com/questions/4659929/how-to-use-utf-8-in-resource-properties-with-resourcebundle + private static ResourceBundle.Control UTF8_RESOURCEBUNDLE_CONTROL = new ResourceBundle.Control() { + @Override + public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) throws IllegalAccessException, InstantiationException, IOException { + String bundleName = toBundleName(baseName, locale); + String resourceName = toResourceName(bundleName, "properties"); + try (InputStream is = loader.getResourceAsStream(resourceName)) { + return new PropertyResourceBundle(new InputStreamReader(is, StandardCharsets.UTF_8)); + } + } + }; + + private static ResourceBundle bundle = ResourceBundle.getBundle("messages", Locale.ENGLISH, UTF8_RESOURCEBUNDLE_CONTROL); + + private MessageUtils() { + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/NumericUtils.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/NumericUtils.java new file mode 100644 index 000000000000..550c516d3086 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/NumericUtils.java @@ -0,0 +1,105 @@ +/* + * 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.util; + +import java.util.Arrays; + +import com.google.common.base.Strings; + +/** Utilities for numeric values */ +public class NumericUtils { + + public static int[] convertToIntArray(String value, boolean ignoreException) throws NumberFormatException { + if (Strings.isNullOrEmpty(value)) { + return new int[]{0}; + } + try { + return Arrays.stream(value.trim().split(",")).mapToInt(Integer::parseInt).toArray(); + } catch (NumberFormatException e) { + if (ignoreException) { + return new int[]{0}; + } else { + throw e; + } + } + } + + public static long[] convertToLongArray(String value, boolean ignoreException) throws NumberFormatException { + if (Strings.isNullOrEmpty(value)) { + return new long[]{0}; + } + try { + return Arrays.stream(value.trim().split(",")).mapToLong(Long::parseLong).toArray(); + } catch (NumberFormatException e) { + if (ignoreException) { + return new long[]{0}; + } else { + throw e; + } + } + } + + public static float[] convertToFloatArray(String value, boolean ignoreException) throws NumberFormatException { + if (Strings.isNullOrEmpty(value)) { + return new float[]{0}; + } + try { + String[] strVals = value.trim().split(","); + float[] values = new float[strVals.length]; + for (int i = 0; i < strVals.length; i++) { + values[i] = Float.parseFloat(strVals[i]); + } + return values; + } catch (NumberFormatException e) { + if (ignoreException) { + return new float[]{0}; + } else { + throw e; + } + } + } + + public static double[] convertToDoubleArray(String value, boolean ignoreException) throws NumberFormatException { + if (Strings.isNullOrEmpty(value)) { + return new double[]{0}; + } + try { + return Arrays.stream(value.trim().split(",")).mapToDouble(Double::parseDouble).toArray(); + } catch (NumberFormatException e) { + if (ignoreException) { + return new double[]{0}; + } else { + throw e; + } + } + } + + public static long tryConvertToLongValue(String value) throws NumberFormatException { + try { + // try parse to long + return Long.parseLong(value.trim()); + } catch (NumberFormatException e) { + // try parse to double + double dvalue = Double.parseDouble(value.trim()); + return org.apache.lucene.util.NumericUtils.doubleToSortableLong(dvalue); + } + } + + private NumericUtils() { + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/StyleConstants.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/StyleConstants.java new file mode 100644 index 000000000000..3b70265cf871 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/StyleConstants.java @@ -0,0 +1,43 @@ +/* + * 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.util; + +import java.awt.Color; +import java.awt.Font; + +/** Constants for the default styles */ +public class StyleConstants { + + public static Font FONT_BUTTON_LARGE = new Font("SanSerif", Font.PLAIN, 15); + + public static Font FONT_MONOSPACE_LARGE = new Font("monospaced", Font.PLAIN, 12); + + public static Color LINK_COLOR = Color.decode("#0099ff"); + + public static Color DISABLED_COLOR = Color.decode("#d9d9d9"); + + public static int TABLE_ROW_HEIGHT_DEFAULT = 18; + + public static int TABLE_COLUMN_MARGIN_DEFAULT = 10; + + public static int TABLE_ROW_MARGIN_DEFAULT = 3; + + private StyleConstants() { + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TabUtils.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TabUtils.java new file mode 100644 index 000000000000..25e0cca6ec87 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TabUtils.java @@ -0,0 +1,40 @@ +/* + * 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.util; + +import javax.swing.JTabbedPane; +import javax.swing.UIManager; +import java.awt.Graphics; + +public class TabUtils { + + public static void forceTransparent(JTabbedPane tabbedPane) { + String lookAndFeelClassName = UIManager.getLookAndFeel().getClass().getName(); + if (lookAndFeelClassName.contains("AquaLookAndFeel")) { + // may be running on mac OS. nothing to do. + return; + } + // https://coderanch.com/t/600541/java/JtabbedPane-transparency + tabbedPane.setUI(new javax.swing.plaf.metal.MetalTabbedPaneUI() { + protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) { + } + }); + } + + private TabUtils(){} +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TableUtils.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TableUtils.java new file mode 100644 index 000000000000..cea72aea1fd8 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TableUtils.java @@ -0,0 +1,85 @@ +/* + * 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.util; + +import javax.swing.JTable; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableModel; +import java.awt.Color; +import java.awt.event.MouseListener; +import java.util.Arrays; +import java.util.TreeMap; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; + +import org.apache.lucene.luke.app.desktop.components.TableColumnInfo; + +/** Table utilities */ +public class TableUtils { + + public static void setupTable(JTable table, int selectionModel, TableModel model, MouseListener mouseListener, + int... colWidth) { + table.setFillsViewportHeight(true); + table.setFont(StyleConstants.FONT_MONOSPACE_LARGE); + table.setRowHeight(StyleConstants.TABLE_ROW_HEIGHT_DEFAULT); + table.setShowHorizontalLines(true); + table.setShowVerticalLines(false); + table.setGridColor(Color.lightGray); + table.getColumnModel().setColumnMargin(StyleConstants.TABLE_COLUMN_MARGIN_DEFAULT); + table.setRowMargin(StyleConstants.TABLE_ROW_MARGIN_DEFAULT); + table.setSelectionMode(selectionModel); + if (model != null) { + table.setModel(model); + } else { + table.setModel(new DefaultTableModel()); + } + if (mouseListener != null) { + table.removeMouseListener(mouseListener); + table.addMouseListener(mouseListener); + } + for (int i = 0; i < colWidth.length; i++) { + table.getColumnModel().getColumn(i).setMinWidth(colWidth[i]); + table.getColumnModel().getColumn(i).setMaxWidth(colWidth[i]); + } + } + + public static void setEnabled(JTable table, boolean enabled) { + table.setEnabled(enabled); + if (enabled) { + table.setRowSelectionAllowed(true); + table.setForeground(Color.black); + table.setBackground(Color.white); + } else { + table.setRowSelectionAllowed(false); + table.setForeground(Color.gray); + table.setBackground(Color.lightGray); + } + } + + public static String[] columnNames(T[] columns) { + return columnMap(columns).entrySet().stream().map(e -> e.getValue().getColName()).toArray(String[]::new); + } + + public static TreeMap columnMap(T[] columns) { + return Arrays.stream(columns).collect(Collectors.toMap(T::getIndex, UnaryOperator.identity(), (e1, e2) -> e1, TreeMap::new)); + } + + private TableUtils() { + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TextAreaAppender.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TextAreaAppender.java new file mode 100644 index 000000000000..06f284602502 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TextAreaAppender.java @@ -0,0 +1,91 @@ +/* + * 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.util; + +import javax.swing.JTextArea; +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.layout.PatternLayout; + +/** Log appender for text areas */ +@Plugin(name = "TextAreaAppender", category = "Core", elementType = "appender", printObject = true) +public final class TextAreaAppender extends AbstractAppender { + + private static JTextArea textArea; + + private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); + private final Lock readLock = rwLock.readLock(); + + protected TextAreaAppender(String name, Filter filter, + Layout layout, final boolean ignoreExceptions) { + super(name, filter, layout, ignoreExceptions); + } + + public static void setTextArea(JTextArea ta) { + if (textArea != null) { + throw new IllegalStateException("TextArea already set."); + } + textArea = ta; + } + + @Override + public void append(LogEvent event) { + if (textArea == null) { + throw new IllegalStateException(); + } + + readLock.lock(); + try { + String message = new String(getLayout().toByteArray(event), StandardCharsets.UTF_8); + textArea.append(message); + } finally { + readLock.unlock(); + } + } + + @PluginFactory + public static TextAreaAppender createAppender( + @PluginAttribute("name") String name, + @PluginElement("Layout") Layout layout, + @PluginElement("Filter") final Filter filter, + @PluginAttribute("otherAttribute") String otherAttribute + ) { + if (name == null) { + LOGGER.error("No name provided for MyCustomAppenderImpl"); + return null; + } + if (layout == null) { + layout = PatternLayout.createDefaultLayout(); + } + + return new TextAreaAppender(name, filter, layout, true); + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TextAreaPrintStream.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TextAreaPrintStream.java new file mode 100644 index 000000000000..3ce150b15a8f --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TextAreaPrintStream.java @@ -0,0 +1,67 @@ +/* + * 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.util; + +import javax.swing.JTextArea; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import org.slf4j.Logger; + +/** PrintStream for text areas */ +public final class TextAreaPrintStream extends PrintStream { + + private Logger logger; + + private ByteArrayOutputStream baos; + + private JTextArea textArea; + + public TextAreaPrintStream(JTextArea textArea, ByteArrayOutputStream baos, Charset charset, Logger logger) throws UnsupportedEncodingException { + super(baos, false, charset.name()); + this.baos = baos; + this.textArea = textArea; + this.logger = logger; + baos.reset(); + } + + @Override + public void println(String s) { + try { + baos.write(s.getBytes(StandardCharsets.UTF_8)); + baos.write('\n'); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + } + + @Override + public void flush() { + try { + textArea.append(baos.toString(StandardCharsets.UTF_8.name())); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } finally { + baos.reset(); + } + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/URLLabel.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/URLLabel.java new file mode 100644 index 000000000000..4b6e71bf0fe7 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/URLLabel.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.util; + +import javax.swing.JLabel; +import java.awt.Cursor; +import java.awt.Desktop; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; + +import org.apache.lucene.luke.models.LukeException; + +/** JLabel extension for representing urls */ +public final class URLLabel extends JLabel { + + private final URL link; + + public URLLabel(String text) { + super(text); + + try { + this.link = new URL(text); + } catch (MalformedURLException e) { + throw new LukeException(e.getMessage(), e); + } + + setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + + addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + openUrl(link); + } + }); + } + + private void openUrl(URL link) { + if (Desktop.isDesktopSupported()) { + try { + Desktop.getDesktop().browse(link.toURI()); + } catch (IOException | URISyntaxException e) { + throw new LukeException(e.getMessage(), e); + } + } + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/lang/Callable.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/lang/Callable.java new file mode 100644 index 000000000000..f5ddf2fee88d --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/lang/Callable.java @@ -0,0 +1,24 @@ +/* + * 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.util.lang; + +/** Functional interface which provides sole method call() */ +@FunctionalInterface +public interface Callable { + void call(); +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/lang/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/lang/package-info.java new file mode 100644 index 000000000000..5cf30577ae9d --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/lang/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. + */ + +/** Syntax sugars / helpers */ +package org.apache.lucene.luke.app.desktop.util.lang; \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/package-info.java new file mode 100644 index 000000000000..bd43e1e5f96b --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/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. + */ + +/** Utilities for the UI components */ +package org.apache.lucene.luke.app.desktop.util; \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/app/package-info.java new file mode 100644 index 000000000000..8e7ea9e4f4d9 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/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. + */ + +/** Views (UIs) for Luke */ +package org.apache.lucene.luke.app; \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/LukeException.java b/lucene/luke/src/java/org/apache/lucene/luke/models/LukeException.java new file mode 100644 index 000000000000..d8bcbfa34ae1 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/LukeException.java @@ -0,0 +1,35 @@ +/* + * 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.models; + +/** Wrapper exception class to convert checked exceptions to runtime exceptions. */ +public class LukeException extends RuntimeException { + + public LukeException(String message, Throwable cause) { + super(message, cause); + } + + public LukeException(Throwable cause) { + super(cause); + } + + public LukeException(String message) { + super(message); + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/LukeModel.java b/lucene/luke/src/java/org/apache/lucene/luke/models/LukeModel.java new file mode 100644 index 000000000000..96fc63640206 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/LukeModel.java @@ -0,0 +1,67 @@ +/* + * 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.models; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.Collection; + +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexCommit; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.luke.models.util.IndexUtils; +import org.apache.lucene.store.Directory; + +/** + * Abstract model class. It holds index reader object and provides basic features for all concrete sub classes. + */ +public abstract class LukeModel { + + protected Directory dir; + + protected IndexReader reader; + + protected IndexCommit commit; + + protected LukeModel(@Nonnull IndexReader reader) { + this.reader = reader; + + if (reader instanceof DirectoryReader) { + DirectoryReader dr = (DirectoryReader) reader; + this.dir = dr.directory(); + try { + this.commit = dr.getIndexCommit(); + } catch (IOException e) { + throw new LukeException(e.getMessage(), e); + } + } else { + this.dir = null; + this.commit = null; + } + + } + + protected LukeModel(@Nonnull Directory dir) { + this.dir = dir; + } + + public Collection getFieldNames() { + return IndexUtils.getFieldNames(reader); + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/analysis/Analysis.java b/lucene/luke/src/java/org/apache/lucene/luke/models/analysis/Analysis.java new file mode 100644 index 000000000000..0da8b016f711 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/analysis/Analysis.java @@ -0,0 +1,154 @@ +/* + * 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.models.analysis; + +import javax.annotation.Nonnull; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.util.CharFilterFactory; +import org.apache.lucene.analysis.util.TokenFilterFactory; +import org.apache.lucene.analysis.util.TokenizerFactory; +import org.apache.lucene.luke.models.LukeException; + +/** + * A dedicated interface for Luke's Analysis tab. + */ +public interface Analysis { + + /** + * Holder for a token. + */ + class Token { + private final String term; + private final List attributes; + + Token(@Nonnull String term, @Nonnull List attributes) { + this.term = term; + this.attributes = attributes; + } + + /** + * Returns the string representation of this token. + */ + public String getTerm() { + return term; + } + + /** + * Returns attributes of this token. + */ + public List getAttributes() { + return ImmutableList.copyOf(attributes); + } + } + + /** + * Holder for a token attribute. + */ + class TokenAttribute { + private final String attClass; + private final Map attValues; + + TokenAttribute(@Nonnull String attClass, @Nonnull Map attValues) { + this.attClass = attClass; + this.attValues = attValues; + } + + /** + * Returns attribute class name. + */ + public String getAttClass() { + return attClass; + } + + /** + * Returns value of this attribute. + */ + public Map getAttValues() { + return ImmutableMap.copyOf(attValues); + } + } + + /** + * Returns built-in {@link Analyzer}s. + */ + Collection> getPresetAnalyzerTypes(); + + /** + * Returns available char filter names. + */ + Collection getAvailableCharFilters(); + + /** + * Returns available tokenizer names. + */ + Collection getAvailableTokenizers(); + + /** + * Returns available token filter names. + */ + Collection getAvailableTokenFilters(); + + /** + * Creates new Analyzer instance for the specified class name. + * + * @param analyzerType - instantiable class name of an Analyzer + * @return new Analyzer instance + * @throws LukeException - if failed to create new Analyzer instance + */ + Analyzer createAnalyzerFromClassName(String analyzerType); + + /** + * Creates new custom Analyzer instance with the given configurations. + * + * @param config - custom analyzer configurations + * @return new Analyzer instance + * @throws LukeException - if failed to create new Analyzer instance + */ + Analyzer buildCustomAnalyzer(CustomAnalyzerConfig config); + + /** + * Analyzes given text with the current Analyzer. + * + * @param text - text string to analyze + * @return the list of token + * @throws LukeException - if an internal error occurs when analyzing text + */ + List analyze(String text); + + /** + * Returns current analyzer. + * + * @throws LukeException - if current analyzer not set + */ + Analyzer currentAnalyzer(); + + /** + * Adds external jar files to classpath and loads custom {@link CharFilterFactory}s, {@link TokenizerFactory}s, or {@link TokenFilterFactory}s. + * + * @param jarFiles - list of paths to jar file + * @throws LukeException - if an internal error occurs when loading jars + */ + void addExternalJars(List jarFiles); + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/analysis/AnalysisFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/models/analysis/AnalysisFactory.java new file mode 100644 index 000000000000..8fa49c6162c7 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/analysis/AnalysisFactory.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.models.analysis; + +/** Factory of {@link Analysis} */ +public class AnalysisFactory { + + public Analysis newInstance() { + return new AnalysisImpl(); + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/analysis/AnalysisImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/models/analysis/AnalysisImpl.java new file mode 100644 index 000000000000..49b41594438e --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/analysis/AnalysisImpl.java @@ -0,0 +1,220 @@ +/* + * 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.models.analysis; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableList; +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.TokenStream; +import org.apache.lucene.analysis.custom.CustomAnalyzer; +import org.apache.lucene.analysis.standard.StandardAnalyzer; +import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; +import org.apache.lucene.analysis.util.CharFilterFactory; +import org.apache.lucene.analysis.util.TokenFilterFactory; +import org.apache.lucene.analysis.util.TokenizerFactory; +import org.apache.lucene.luke.models.LukeException; +import org.apache.lucene.util.AttributeImpl; +import org.reflections.Reflections; +import org.reflections.scanners.SubTypesScanner; +import org.reflections.util.ClasspathHelper; +import org.reflections.util.ConfigurationBuilder; +import org.reflections.util.FilterBuilder; + +/** Default implementation of {@link AnalysisImpl} */ +public final class AnalysisImpl implements Analysis { + + private final List> presetAnalyzerTypes; + + private Analyzer analyzer; + + public AnalysisImpl() { + presetAnalyzerTypes = new ArrayList<>(); + for (Class clazz : getInstantiableSubTypesBuiltIn(Analyzer.class)) { + try { + // add to presets if no args constructor is available + clazz.getConstructor(); + presetAnalyzerTypes.add(clazz); + } catch (NoSuchMethodException e) { + } + } + // standard analyzer cannot be found, so manually add it... + presetAnalyzerTypes.add(StandardAnalyzer.class); + presetAnalyzerTypes.sort(Comparator.comparing(Class::getName)); + } + + @Override + public void addExternalJars(List jarFiles) { + List urls = new ArrayList<>(); + + for (String jarFile : jarFiles) { + Path path = FileSystems.getDefault().getPath(jarFile); + if (!Files.exists(path) || !jarFile.endsWith(".jar")) { + throw new LukeException(String.format(Locale.ENGLISH, "Invalid jar file path: %s", jarFile)); + } + try { + URL url = path.toUri().toURL(); + urls.add(url); + } catch (IOException e) { + throw new LukeException(e.getMessage(), e); + } + } + + // reload available tokenizers, charfilters, and tokenfilters + URLClassLoader classLoader = new URLClassLoader( + urls.toArray(new URL[0]), ClassLoader.getSystemClassLoader()); + CharFilterFactory.reloadCharFilters(classLoader); + TokenizerFactory.reloadTokenizers(classLoader); + TokenFilterFactory.reloadTokenFilters(classLoader); + } + + @Override + public Collection> getPresetAnalyzerTypes() { + return ImmutableList.copyOf(presetAnalyzerTypes); + } + + @Override + public Collection getAvailableCharFilters() { + return CharFilterFactory.availableCharFilters().stream().sorted().collect(Collectors.toList()); + } + + @Override + public Collection getAvailableTokenizers() { + return TokenizerFactory.availableTokenizers().stream().sorted().collect(Collectors.toList()); + } + + @Override + public Collection getAvailableTokenFilters() { + return TokenFilterFactory.availableTokenFilters().stream().sorted().collect(Collectors.toList()); + } + + private List> getInstantiableSubTypesBuiltIn(Class superType) { + Reflections reflections = new Reflections(new ConfigurationBuilder() + .setUrls(ClasspathHelper.forPackage("org.apache.lucene")) + .setScanners(new SubTypesScanner()) + .filterInputsBy(new FilterBuilder().include("org\\.apache\\.lucene\\.analysis.*"))); + return reflections.getSubTypesOf(superType).stream() + .filter(type -> !Modifier.isAbstract(type.getModifiers())) + .filter(type -> !type.getSimpleName().startsWith("Mock")) + .collect(Collectors.toList()); + } + + @Override + public List analyze(@Nonnull String text) { + if (analyzer == null) { + throw new LukeException("Analyzer is not set."); + } + + try { + List result = new ArrayList<>(); + + TokenStream stream = analyzer.tokenStream("", text); + stream.reset(); + + CharTermAttribute charAtt = stream.getAttribute(CharTermAttribute.class); + + // iterate tokens + while (stream.incrementToken()) { + List attributes = new ArrayList<>(); + Iterator itr = stream.getAttributeImplsIterator(); + + while (itr.hasNext()) { + AttributeImpl att = itr.next(); + Map attValues = new LinkedHashMap<>(); + att.reflectWith((attClass, key, value) -> { + if (value != null) + attValues.put(key, value.toString()); + }); + attributes.add(new TokenAttribute(att.getClass().getSimpleName(), attValues)); + } + + result.add(new Token(charAtt.toString(), attributes)); + } + stream.close(); + + return result; + } catch (IOException e) { + throw new LukeException(e.getMessage(), e); + } + } + + @Override + public Analyzer createAnalyzerFromClassName(@Nonnull String analyzerType) { + try { + Class clazz = Class.forName(analyzerType).asSubclass(Analyzer.class); + this.analyzer = clazz.newInstance(); + return analyzer; + } catch (ReflectiveOperationException e) { + throw new LukeException(String.format(Locale.ENGLISH, "Failed to instantiate class: %s", analyzerType), e); + } + } + + @Override + public Analyzer buildCustomAnalyzer(@Nonnull CustomAnalyzerConfig config) { + try { + // create builder + CustomAnalyzer.Builder builder = config.getConfigDir() + .map(path -> CustomAnalyzer.builder(FileSystems.getDefault().getPath(path))) + .orElse(CustomAnalyzer.builder()); + + // set tokenizer + builder.withTokenizer(config.getTokenizerConfig().getName(), config.getTokenizerConfig().getParams()); + + // add char filters + for (CustomAnalyzerConfig.ComponentConfig cfConf : config.getCharFilterConfigs()) { + builder.addCharFilter(cfConf.getName(), cfConf.getParams()); + } + + // add token filters + for (CustomAnalyzerConfig.ComponentConfig tfConf : config.getTokenFilterConfigs()) { + builder.addTokenFilter(tfConf.getName(), tfConf.getParams()); + } + + // build analyzer + this.analyzer = builder.build(); + return analyzer; + } catch (Exception e) { + throw new LukeException("Failed to build custom analyzer.", e); + } + } + + @Override + public Analyzer currentAnalyzer() { + if (analyzer == null) { + throw new LukeException("Analyzer is not set."); + } + return analyzer; + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/analysis/CustomAnalyzerConfig.java b/lucene/luke/src/java/org/apache/lucene/luke/models/analysis/CustomAnalyzerConfig.java new file mode 100644 index 000000000000..6350f78c7c85 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/analysis/CustomAnalyzerConfig.java @@ -0,0 +1,128 @@ +/* + * 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.models.analysis; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import com.google.common.collect.ImmutableList; + +/** + * Configurations for a custom analyzer. + */ +public final class CustomAnalyzerConfig { + + private final String configDir; + + private final ComponentConfig tokenizerConfig; + + private final List charFilterConfigs; + + private final List tokenFilterConfigs; + + /** {@link CustomAnalyzerConfig} builder */ + public static class Builder { + private String configDir; + private final ComponentConfig tokenizerConfig; + private final List charFilterConfigs = new ArrayList<>(); + private final List tokenFilterConfigs = new ArrayList<>(); + + public Builder(@Nonnull String name, @Nonnull Map tokenizerParams) { + tokenizerConfig = new ComponentConfig(name, new HashMap<>(tokenizerParams)); + } + + public Builder configDir(String val) { + configDir = val; + return this; + } + + public Builder addCharFilterConfig(@Nonnull String name, @Nonnull Map params) { + charFilterConfigs.add(new ComponentConfig(name, new HashMap<>(params))); + return this; + } + + public Builder addTokenFilterConfig(@Nonnull String name, @Nonnull Map params) { + tokenFilterConfigs.add(new ComponentConfig(name, new HashMap<>(params))); + return this; + } + + public CustomAnalyzerConfig build() { + return new CustomAnalyzerConfig(this); + } + } + + private CustomAnalyzerConfig(Builder builder) { + this.tokenizerConfig = builder.tokenizerConfig; + this.configDir = builder.configDir; + this.charFilterConfigs = builder.charFilterConfigs; + this.tokenFilterConfigs = builder.tokenFilterConfigs; + } + + /** + * Returns directory path for configuration files, or empty. + */ + Optional getConfigDir() { + return Optional.ofNullable(configDir); + } + + /** + * Returns Tokenizer configurations. + */ + ComponentConfig getTokenizerConfig() { + return tokenizerConfig; + } + + /** + * Returns CharFilters configurations. + */ + List getCharFilterConfigs() { + return ImmutableList.copyOf(charFilterConfigs); + } + + /** + * Returns TokenFilters configurations. + */ + List getTokenFilterConfigs() { + return ImmutableList.copyOf(tokenFilterConfigs); + } + + static class ComponentConfig { + + /* SPI name */ + private final String name; + /* parameter map */ + private final Map params; + + ComponentConfig(@Nonnull String name, @Nonnull Map params) { + this.name = name; + this.params = params; + } + + String getName() { + return this.name; + } + + Map getParams() { + return this.params; + } + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/analysis/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/models/analysis/package-info.java new file mode 100644 index 000000000000..52a9c0c087db --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/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. + */ + +/** Models and APIs for the Analysis tab */ +package org.apache.lucene.luke.models.analysis; \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/commits/Commit.java b/lucene/luke/src/java/org/apache/lucene/luke/models/commits/Commit.java new file mode 100644 index 000000000000..73f1594a11c9 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/commits/Commit.java @@ -0,0 +1,68 @@ +/* + * 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.models.commits; + +import java.io.IOException; + +import org.apache.lucene.index.IndexCommit; +import org.apache.lucene.luke.models.util.IndexUtils; + +/** + * Holder for a commit. + */ +public final class Commit { + + private long generation; + + private boolean isDeleted; + + private int segCount; + + private String userData; + + static Commit of(IndexCommit ic) { + Commit commit = new Commit(); + commit.generation = ic.getGeneration(); + commit.isDeleted = ic.isDeleted(); + commit.segCount = ic.getSegmentCount(); + try { + commit.userData = IndexUtils.getCommitUserData(ic); + } catch (IOException e) { + } + return commit; + } + + public long getGeneration() { + return generation; + } + + public boolean isDeleted() { + return isDeleted; + } + + public int getSegCount() { + return segCount; + } + + public String getUserData() { + return userData; + } + + private Commit() { + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/commits/Commits.java b/lucene/luke/src/java/org/apache/lucene/luke/models/commits/Commits.java new file mode 100644 index 000000000000..c744714f5bc9 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/commits/Commits.java @@ -0,0 +1,89 @@ +/* + * 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.models.commits; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.luke.models.LukeException; + +/** + * A dedicated interface for Luke's Commits tab. + */ +public interface Commits { + + /** + * Returns commits that exists in this Directory. + * + * @throws LukeException - if an internal error occurs when accessing index + */ + List listCommits(); + + /** + * Returns a commit of the specified generation. + * + * @param commitGen - generation + * @throws LukeException - if an internal error occurs when accessing index + */ + Optional getCommit(long commitGen); + + /** + * Returns index files for the specified generation. + * + * @param commitGen - generation + * @throws LukeException - if an internal error occurs when accessing index + */ + List getFiles(long commitGen); + + /** + * Returns segments for the specified generation. + * + * @param commitGen - generation + * @throws LukeException - if an internal error occurs when accessing index + */ + List getSegments(long commitGen); + + /** + * Returns internal codec attributes map for the specified segment. + * + * @param commitGen - generation + * @param name - segment name + * @throws LukeException - if an internal error occurs when accessing index + */ + Map getSegmentAttributes(long commitGen, String name); + + /** + * Returns diagnotics for the specified segment. + * + * @param commitGen - generation + * @param name - segment name + * @throws LukeException - if an internal error occurs when accessing index + */ + Map getSegmentDiagnostics(long commitGen, String name); + + /** + * Returns codec for the specified segment. + * + * @param commitGen - generation + * @param name - segment name + * @throws LukeException - if an internal error occurs when accessing index + */ + Optional getSegmentCodec(long commitGen, String name); +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/commits/CommitsFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/models/commits/CommitsFactory.java new file mode 100644 index 000000000000..22d959d86210 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/commits/CommitsFactory.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.models.commits; + +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.store.Directory; + +/** Factory of {@link Commits} */ +public class CommitsFactory { + + public Commits newInstance(Directory dir, String indexPath) { + return new CommitsImpl(dir, indexPath); + } + + public Commits newInstance(DirectoryReader reader, String indexPath) { + return new CommitsImpl(reader, indexPath); + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/commits/CommitsImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/models/commits/CommitsImpl.java new file mode 100644 index 000000000000..07dda53e0680 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/commits/CommitsImpl.java @@ -0,0 +1,225 @@ +/* + * 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.models.commits; + +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableMap; +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexCommit; +import org.apache.lucene.index.SegmentInfos; +import org.apache.lucene.luke.models.LukeException; +import org.apache.lucene.luke.models.LukeModel; +import org.apache.lucene.store.Directory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Default implementation of {@link Commits} */ +public final class CommitsImpl extends LukeModel implements Commits { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private final String indexPath; + + private final Map commitMap; + + /** + * Constructs a CommitsImpl that holds given {@link Directory}. + * + * @param dir - the index directory + * @param indexPath - the path to index directory + */ + public CommitsImpl(Directory dir, String indexPath) { + super(dir); + this.indexPath = indexPath; + this.commitMap = initCommitMap(); + } + + /** + * Constructs a CommitsImpl that holds the {@link Directory} wrapped in the given {@link DirectoryReader}. + * + * @param reader - the index reader + * @param indexPath - the path to index directory + */ + public CommitsImpl(DirectoryReader reader, String indexPath) { + super(reader.directory()); + this.indexPath = indexPath; + this.commitMap = initCommitMap(); + } + + private Map initCommitMap() { + try { + List indexCommits = DirectoryReader.listCommits(dir); + Map map = new TreeMap<>(); + for (IndexCommit ic : indexCommits) { + map.put(ic.getGeneration(), ic); + } + return map; + } catch (IOException e) { + throw new LukeException("Failed to get commits list.", e); + } + } + + @Override + public List listCommits() throws LukeException { + List commits = getCommitMap().values().stream() + .map(Commit::of) + .collect(Collectors.toList()); + Collections.reverse(commits); + return commits; + } + + @Override + public Optional getCommit(long commitGen) throws LukeException { + IndexCommit ic = getCommitMap().get(commitGen); + + if (ic == null) { + String msg = String.format(Locale.ENGLISH, "Commit generation %d not exists.", commitGen); + log.warn(msg); + return Optional.empty(); + } + + return Optional.of(Commit.of(ic)); + } + + @Override + public List getFiles(long commitGen) throws LukeException { + IndexCommit ic = getCommitMap().get(commitGen); + + if (ic == null) { + String msg = String.format(Locale.ENGLISH, "Commit generation %d not exists.", commitGen); + log.warn(msg); + return Collections.emptyList(); + } + + try { + return ic.getFileNames().stream() + .map(name -> File.of(indexPath, name)) + .sorted(Comparator.comparing(File::getFileName)) + .collect(Collectors.toList()); + } catch (IOException e) { + throw new LukeException(String.format(Locale.ENGLISH, "Failed to load files for commit generation %d", commitGen), e); + } + } + + @Override + public List getSegments(long commitGen) throws LukeException { + try { + SegmentInfos infos = findSegmentInfos(commitGen); + if (infos == null) { + return Collections.emptyList(); + } + + return infos.asList().stream() + .map(Segment::of) + .sorted(Comparator.comparing(Segment::getName)) + .collect(Collectors.toList()); + } catch (IOException e) { + throw new LukeException(String.format(Locale.ENGLISH, "Failed to load segment infos for commit generation %d", commitGen), e); + } + } + + @Override + public Map getSegmentAttributes(long commitGen, String name) throws LukeException { + try { + SegmentInfos infos = findSegmentInfos(commitGen); + if (infos == null) { + return Collections.emptyMap(); + } + + return infos.asList().stream() + .filter(seg -> seg.info.name.equals(name)) + .findAny() + .map(seg -> seg.info.getAttributes()) + .orElse(Collections.emptyMap()); + } catch (IOException e) { + throw new LukeException(String.format(Locale.ENGLISH, "Failed to load segment infos for commit generation %d", commitGen), e); + } + } + + @Override + public Map getSegmentDiagnostics(long commitGen, String name) throws LukeException { + try { + SegmentInfos infos = findSegmentInfos(commitGen); + if (infos == null) { + return Collections.emptyMap(); + } + + return infos.asList().stream() + .filter(seg -> seg.info.name.equals(name)) + .findAny() + .map(seg -> seg.info.getDiagnostics()) + .orElse(Collections.emptyMap()); + } catch (IOException e) { + throw new LukeException(String.format(Locale.ENGLISH, "Failed to load segment infos for commit generation %d", commitGen), e); + } + } + + @Override + public Optional getSegmentCodec(long commitGen, String name) throws LukeException { + try { + SegmentInfos infos = findSegmentInfos(commitGen); + if (infos == null) { + return Optional.empty(); + } + + return infos.asList().stream() + .filter(seg -> seg.info.name.equals(name)) + .findAny() + .map(seg -> seg.info.getCodec()); + } catch (IOException e) { + throw new LukeException(String.format(Locale.ENGLISH, "Failed to load segment infos for commit generation %d", commitGen), e); + } + } + + private Map getCommitMap() throws LukeException { + if (dir == null) { + return Collections.emptyMap(); + } + return ImmutableMap.copyOf(commitMap); + } + + private SegmentInfos findSegmentInfos(long commitGen) throws LukeException, IOException { + IndexCommit ic = getCommitMap().get(commitGen); + if (ic == null) { + return null; + } + String segmentFile = ic.getSegmentsFileName(); + return SegmentInfos.readCommit(dir, segmentFile); + } + + static String toDisplaySize(long size) { + if (size < 1024) { + return String.valueOf(size) + " B"; + } else if (size < 1048576) { + return String.valueOf(size / 1024) + " KB"; + } else { + return String.valueOf(size / 1048576) + " MB"; + } + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/commits/File.java b/lucene/luke/src/java/org/apache/lucene/luke/models/commits/File.java new file mode 100644 index 000000000000..8038b39be3b7 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/commits/File.java @@ -0,0 +1,52 @@ +/* + * 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.models.commits; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * Holder for a index file. + */ +public final class File { + private String fileName; + private String displaySize; + + static File of(String indexPath, String name) { + File file = new File(); + file.fileName = name; + try { + file.displaySize = CommitsImpl.toDisplaySize(Files.size(Paths.get(indexPath, name))); + } catch (IOException e) { + file.displaySize = "unknown"; + } + return file; + } + + public String getFileName() { + return fileName; + } + + public String getDisplaySize() { + return displaySize; + } + + private File() { + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/commits/Segment.java b/lucene/luke/src/java/org/apache/lucene/luke/models/commits/Segment.java new file mode 100644 index 000000000000..cea86e2ec9f7 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/commits/Segment.java @@ -0,0 +1,95 @@ +/* + * 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.models.commits; + +import java.io.IOException; + +import org.apache.lucene.index.SegmentCommitInfo; + +/** + * Holder for a segment. + */ +public final class Segment { + + private String name; + + private int maxDoc; + + private long delGen; + + private int delCount; + + private String luceneVer; + + private String codecName; + + private String displaySize; + + private boolean useCompoundFile; + + static Segment of(SegmentCommitInfo segInfo) { + Segment segment = new Segment(); + segment.name = segInfo.info.name; + segment.maxDoc = segInfo.info.maxDoc(); + segment.delGen = segInfo.getDelGen(); + segment.delCount = segInfo.getDelCount(); + segment.luceneVer = segInfo.info.getVersion().toString(); + segment.codecName = segInfo.info.getCodec().getName(); + try { + segment.displaySize = CommitsImpl.toDisplaySize(segInfo.sizeInBytes()); + } catch (IOException e) { + } + segment.useCompoundFile = segInfo.info.getUseCompoundFile(); + return segment; + } + + public String getName() { + return name; + } + + public int getMaxDoc() { + return maxDoc; + } + + public long getDelGen() { + return delGen; + } + + public int getDelCount() { + return delCount; + } + + public String getLuceneVer() { + return luceneVer; + } + + public String getCodecName() { + return codecName; + } + + public String getDisplaySize() { + return displaySize; + } + + public boolean isUseCompoundFile() { + return useCompoundFile; + } + + private Segment() { + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/commits/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/models/commits/package-info.java new file mode 100644 index 000000000000..87ed8a0158d0 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/commits/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. + */ + +/** Models and APIs for the Commits tab */ +package org.apache.lucene.luke.models.commits; \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocValues.java b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocValues.java new file mode 100644 index 000000000000..312f4364d563 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocValues.java @@ -0,0 +1,85 @@ +/* + * 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.models.documents; + +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.lucene.index.DocValuesType; +import org.apache.lucene.util.BytesRef; + +/** + * Holder for doc values. + */ +public final class DocValues { + + private final DocValuesType dvType; + + private final List values; + + private final List numericValues; + + /** + * Returns a new doc values entry representing the specified doc values type and values. + * + * @param dvType - doc values type + * @param values - (string) values + * @param numericValues numeric values + * @return doc values + */ + static DocValues of(DocValuesType dvType, List values, List numericValues) { + return new DocValues(dvType, values, numericValues); + } + + private DocValues(DocValuesType dvType, List values, List numericValues) { + this.dvType = dvType; + this.values = values; + this.numericValues = numericValues; + } + + /** + * Returns the type of this doc values. + */ + public DocValuesType getDvType() { + return dvType; + } + + /** + * Returns the list of (string) values. + */ + public List getValues() { + return values; + } + + /** + * Returns the list of numeric values. + */ + public List getNumericValues() { + return numericValues; + } + + @Override + public String toString() { + String numValuesStr = numericValues.stream().map(String::valueOf).collect(Collectors.joining(",")); + return "DocValues{" + + "dvType=" + dvType + + ", values=" + values + + ", numericValues=[" + numValuesStr + "]" + + '}'; + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocValuesAdapter.java b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocValuesAdapter.java new file mode 100644 index 000000000000..73e699241158 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocValuesAdapter.java @@ -0,0 +1,168 @@ +/* + * 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.models.documents; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.apache.lucene.index.BinaryDocValues; +import org.apache.lucene.index.DocValuesType; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.NumericDocValues; +import org.apache.lucene.index.SortedDocValues; +import org.apache.lucene.index.SortedNumericDocValues; +import org.apache.lucene.index.SortedSetDocValues; +import org.apache.lucene.luke.models.util.IndexUtils; +import org.apache.lucene.util.BytesRef; + +/** + * An utility class to access to the doc values. + */ +final class DocValuesAdapter { + + private final IndexReader reader; + + DocValuesAdapter(@Nonnull IndexReader reader) { + this.reader = reader; + } + + /** + * Returns the doc values for the specified field in the specified document. + * Empty Optional instance is returned if no doc values is available for the field. + * + * @param docid - document id + * @param field - field name + * @return doc values, if exists, or empty + * @throws IOException - if there is a low level IO error. + */ + Optional getDocValues(int docid, String field) throws IOException { + DocValuesType dvType = IndexUtils.getFieldInfo(reader, field).getDocValuesType(); + + switch (dvType) { + case BINARY: + return createBinaryDocValues(docid, field, DocValuesType.BINARY); + case NUMERIC: + return createNumericDocValues(docid, field, DocValuesType.NUMERIC); + case SORTED_NUMERIC: + return createSortedNumericDocValues(docid, field, DocValuesType.SORTED_NUMERIC); + case SORTED: + return createSortedDocValues(docid, field, DocValuesType.SORTED); + case SORTED_SET: + return createSortedSetDocValues(docid, field, DocValuesType.SORTED_SET); + default: + return Optional.empty(); + } + } + + private Optional createBinaryDocValues(int docid, String field, DocValuesType dvType) + throws IOException { + BinaryDocValues bvalues = IndexUtils.getBinaryDocValues(reader, field); + + if (bvalues.advanceExact(docid)) { + DocValues dv = DocValues.of( + dvType, + Collections.singletonList(BytesRef.deepCopyOf(bvalues.binaryValue())), + Collections.emptyList()); + return Optional.of(dv); + } + + return Optional.empty(); + } + + private Optional createNumericDocValues(int docid, String field, DocValuesType dvType) + throws IOException { + NumericDocValues nvalues = IndexUtils.getNumericDocValues(reader, field); + + if (nvalues.advanceExact(docid)) { + DocValues dv = DocValues.of( + dvType, + Collections.emptyList(), + Collections.singletonList(nvalues.longValue()) + ); + return Optional.of(dv); + } + + return Optional.empty(); + } + + private Optional createSortedNumericDocValues(int docid, String field, DocValuesType dvType) + throws IOException { + SortedNumericDocValues snvalues = IndexUtils.getSortedNumericDocValues(reader, field); + + if (snvalues.advanceExact(docid)) { + List numericValues = new ArrayList<>(); + + int dvCount = snvalues.docValueCount(); + for (int i = 0; i < dvCount; i++) { + numericValues.add(snvalues.nextValue()); + } + + DocValues dv = DocValues.of( + dvType, + Collections.emptyList(), + numericValues + ); + return Optional.of(dv); + } + + return Optional.empty(); + } + + private Optional createSortedDocValues(int docid, String field, DocValuesType dvType) + throws IOException { + SortedDocValues svalues = IndexUtils.getSortedDocValues(reader, field); + + if (svalues.advanceExact(docid)) { + DocValues dv = DocValues.of( + dvType, + Collections.singletonList(BytesRef.deepCopyOf(svalues.binaryValue())), + Collections.emptyList() + ); + return Optional.of(dv); + } + + return Optional.empty(); + } + + private Optional createSortedSetDocValues(int docid, String field, DocValuesType dvType) + throws IOException { + SortedSetDocValues ssvalues = IndexUtils.getSortedSetDocvalues(reader, field); + + if (ssvalues.advanceExact(docid)) { + List values = new ArrayList<>(); + + long ord; + while ((ord = ssvalues.nextOrd()) != SortedSetDocValues.NO_MORE_ORDS) { + values.add(BytesRef.deepCopyOf(ssvalues.lookupOrd(ord))); + } + + DocValues dv = DocValues.of( + dvType, + values, + Collections.emptyList() + ); + return Optional.of(dv); + } + + return Optional.empty(); + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocumentField.java b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocumentField.java new file mode 100644 index 000000000000..2597e62a01d0 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocumentField.java @@ -0,0 +1,166 @@ +/* + * 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.models.documents; + +import javax.annotation.Nonnull; +import java.io.IOException; + +import org.apache.lucene.index.DocValuesType; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.IndexOptions; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexableField; +import org.apache.lucene.index.MultiDocValues; +import org.apache.lucene.index.NumericDocValues; +import org.apache.lucene.util.BytesRef; + +/** + * Holder for a document field's information and data. + */ +public final class DocumentField { + + // field name + private String name; + + // index options + private IndexOptions idxOptions; + private boolean hasTermVectors; + private boolean hasPayloads; + private boolean hasNorms; + private long norm; + + // stored value + private boolean isStored; + private String stringValue; + private BytesRef binaryValue; + private Number numericValue; + + // doc values + private DocValuesType dvType; + + // point values + private int pointDimensionCount; + private int pointNumBytes; + + static DocumentField of(@Nonnull FieldInfo finfo, @Nonnull IndexReader reader, int docId) + throws IOException { + return of(finfo, null, reader, docId); + } + + static DocumentField of(@Nonnull FieldInfo finfo, IndexableField field, @Nonnull IndexReader reader, int docId) + throws IOException { + + DocumentField dfield = new DocumentField(); + + dfield.name = finfo.name; + dfield.idxOptions = finfo.getIndexOptions(); + dfield.hasTermVectors = finfo.hasVectors(); + dfield.hasPayloads = finfo.hasPayloads(); + dfield.hasNorms = finfo.hasNorms(); + + if (finfo.hasNorms()) { + NumericDocValues norms = MultiDocValues.getNormValues(reader, finfo.name); + if (norms.advanceExact(docId)) { + dfield.norm = norms.longValue(); + } + } + + dfield.dvType = finfo.getDocValuesType(); + + dfield.pointDimensionCount = finfo.getPointDataDimensionCount(); + dfield.pointNumBytes = finfo.getPointNumBytes(); + + if (field != null) { + dfield.isStored = field.fieldType().stored(); + dfield.stringValue = field.stringValue(); + if (field.binaryValue() != null) { + dfield.binaryValue = BytesRef.deepCopyOf(field.binaryValue()); + } + dfield.numericValue = field.numericValue(); + } + + return dfield; + } + + public String getName() { + return name; + } + + public IndexOptions getIdxOptions() { + return idxOptions; + } + + public boolean hasTermVectors() { + return hasTermVectors; + } + + public boolean hasPayloads() { + return hasPayloads; + } + + public boolean hasNorms() { + return hasNorms; + } + + public long getNorm() { + return norm; + } + + public boolean isStored() { + return isStored; + } + + public String getStringValue() { + return stringValue; + } + + public BytesRef getBinaryValue() { + return binaryValue; + } + + public Number getNumericValue() { + return numericValue; + } + + public DocValuesType getDvType() { + return dvType; + } + + public int getPointDimensionCount() { + return pointDimensionCount; + } + + public int getPointNumBytes() { + return pointNumBytes; + } + + @Override + public String toString() { + return "DocumentField{" + + "name='" + name + '\'' + + ", idxOptions=" + idxOptions + + ", hasTermVectors=" + hasTermVectors + + ", isStored=" + isStored + + ", dvType=" + dvType + + ", pointDimensionCount=" + pointDimensionCount + + '}'; + } + + private DocumentField() { + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/documents/Documents.java b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/Documents.java new file mode 100644 index 000000000000..098cf4b39137 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/Documents.java @@ -0,0 +1,144 @@ +/* + * 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.models.documents; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import org.apache.lucene.index.Term; +import org.apache.lucene.luke.models.LukeException; + +/** + * A dedicated interface for Luke's Documents tab. + */ +public interface Documents { + + /** + * Returns one greater than the largest possible document number. + */ + int getMaxDoc(); + + /** + * Returns field names in this index. + */ + Collection getFieldNames(); + + /** + * Returns true if the document with the specified docid is not deleted, otherwise false. + * + * @param docid - document id + */ + boolean isLive(int docid); + + /** + * Returns the list of field information and field data for the specified document. + * + * @param docid - document id + * @throws LukeException - if an internal error occurs when accessing index + */ + List getDocumentFields(int docid); + + /** + * Returns the current target field name. + */ + String getCurrentField(); + + /** + * Returns the first indexed term in the specified field. + * Empty Optional instance is returned if no terms are available for the field. + * + * @param field - field name + * @throws LukeException - if an internal error occurs when accessing index + */ + Optional firstTerm(String field); + + /** + * Increments the terms iterator and returns the next indexed term for the target field. + * Empty Optional instance is returned if the terms iterator has not been positioned yet, or has been exhausted. + * + * @return next term, if exists, or empty + * @throws LukeException - if an internal error occurs when accessing index + */ + Optional nextTerm(); + + /** + * Seeks to the specified term, if it exists, or to the next (ceiling) term. Returns the term that was found. + * Empty Optional instance is returned if the terms iterator has not been positioned yet, or has been exhausted. + * + * @param termText - term to seek + * @return found term, if exists, or empty + * @throws LukeException - if an internal error occurs when accessing index + */ + Optional seekTerm(String termText); + + /** + * Returns the first document id (posting) associated with the current term. + * Empty Optional instance is returned if the terms iterator has not been positioned yet, or the postings iterator has been exhausted. + * + * @return document id, if exists, or empty + * @throws LukeException - if an internal error occurs when accessing index + */ + Optional firstTermDoc(); + + /** + * Increments the postings iterator and returns the next document id (posting) for the current term. + * Empty Optional instance is returned if the terms iterator has not been positioned yet, or the postings iterator has been exhausted. + * + * @return document id, if exists, or empty + * @throws LukeException - if an internal error occurs when accessing index + */ + Optional nextTermDoc(); + + /** + * Returns the list of the position information for the current posting. + * + * @throws LukeException - if an internal error occurs when accessing index + */ + List getTermPositions(); + + /** + * Returns the document frequency for the current term (the number of documents containing the current term.) + * Empty Optional instance is returned if the terms iterator has not been positioned yet. + * + * @throws LukeException - if an internal error occurs when accessing index + */ + Optional getDocFreq(); + + /** + * Returns the term vectors for the specified field in the specified document. + * If no term vector is available for the field, empty list is returned. + * + * @param docid - document id + * @param field - field name + * @return list of term vector elements + * @throws LukeException - if an internal error occurs when accessing index + */ + List getTermVectors(int docid, String field); + + /** + * Returns the doc values for the specified field in the specified document. + * Empty Optional instance is returned if no doc values is available for the field. + * + * @param docid - document id + * @param field - field name + * @return doc values, if exists, or empty + * @throws LukeException - if an internal error occurs when accessing index + */ + Optional getDocValues(int docid, String field); +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocumentsFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocumentsFactory.java new file mode 100644 index 000000000000..96b0a6fb6e99 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocumentsFactory.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.models.documents; + +import org.apache.lucene.index.IndexReader; + +/** Factory of {@link Documents} */ +public class DocumentsFactory { + + public Documents newInstance(IndexReader reader) { + return new DocumentsImpl(reader); + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocumentsImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocumentsImpl.java new file mode 100644 index 000000000000..8a1ffe960eaf --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocumentsImpl.java @@ -0,0 +1,344 @@ +/* + * 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.models.documents; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Optional; + +import org.apache.lucene.document.Document; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexableField; +import org.apache.lucene.index.PostingsEnum; +import org.apache.lucene.index.Term; +import org.apache.lucene.index.Terms; +import org.apache.lucene.index.TermsEnum; +import org.apache.lucene.luke.models.LukeException; +import org.apache.lucene.luke.models.LukeModel; +import org.apache.lucene.luke.util.BytesRefUtils; +import org.apache.lucene.luke.models.util.IndexUtils; +import org.apache.lucene.util.BytesRef; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Default implementation of {@link Documents} */ +public final class DocumentsImpl extends LukeModel implements Documents { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private final TermVectorsAdapter tvAdapter; + + private final DocValuesAdapter dvAdapter; + + private String curField; + + private TermsEnum tenum; + + private PostingsEnum penum; + + /** + * Constructs an DocumentsImpl that holds given {@link IndexReader}. + * + * @param reader - the index reader + */ + public DocumentsImpl(@Nonnull IndexReader reader) { + super(reader); + this.tvAdapter = new TermVectorsAdapter(reader); + this.dvAdapter = new DocValuesAdapter(reader); + } + + @Override + public int getMaxDoc() { + return reader.maxDoc(); + } + + @Override + public boolean isLive(int docid) { + return IndexUtils.isLive(reader, docid); + } + + @Override + public List getDocumentFields(int docid) { + if (!isLive(docid)) { + log.info("Doc #{} was deleted", docid); + return Collections.emptyList(); + } + + List res = new ArrayList<>(); + + try { + Document doc = reader.document(docid); + + for (FieldInfo finfo : IndexUtils.getFieldInfos(reader)) { + // iterate all fields for this document + IndexableField[] fields = doc.getFields(finfo.name); + if (fields.length == 0) { + // no stored data is available + res.add(DocumentField.of(finfo, reader, docid)); + } else { + for (IndexableField field : fields) { + res.add(DocumentField.of(finfo, field, reader, docid)); + } + } + } + + } catch (IOException e) { + throw new LukeException(String.format(Locale.ENGLISH, "Fields information not available for doc %d.", docid), e); + } + + return res; + } + + @Override + public String getCurrentField() { + return curField; + } + + @Override + public Optional firstTerm(@Nonnull String field) { + try { + Terms terms = IndexUtils.getTerms(reader, field); + + if (terms == null) { + // no such field? + resetCurrentField(); + resetTermsIterator(); + log.warn("Terms not available for field: {}.", field); + return Optional.empty(); + } else { + setCurrentField(field); + setTermsIterator(terms.iterator()); + + if (tenum.next() == null) { + // no term available for this field + resetTermsIterator(); + log.warn("No term available for field: {}.", field); + return Optional.empty(); + } else { + return Optional.of(new Term(curField, tenum.term())); + } + } + + } catch (IOException e) { + resetTermsIterator(); + throw new LukeException(String.format(Locale.ENGLISH, "Terms not available for field: %s.", field), e); + } finally { + // discard current postings enum + resetPostingsIterator(); + } + } + + @Override + public Optional nextTerm() { + if (tenum == null) { + // terms enum not initialized + log.warn("Terms enum un-positioned."); + return Optional.empty(); + } + + try { + if (tenum.next() == null) { + // end of the iterator + resetTermsIterator(); + log.info("Reached the end of the term iterator for field: {}.", curField); + return Optional.empty(); + + } else { + return Optional.of(new Term(curField, tenum.term())); + } + } catch (IOException e) { + resetTermsIterator(); + throw new LukeException(String.format(Locale.ENGLISH, "Terms not available for field: %s.", curField), e); + } finally { + // discard current postings enum + resetPostingsIterator(); + } + } + + @Override + public Optional seekTerm(@Nonnull String termText) { + if (curField == null) { + // field is not selected + log.warn("Field not selected."); + return Optional.empty(); + } + + try { + Terms terms = IndexUtils.getTerms(reader, curField); + setTermsIterator(terms.iterator()); + + if (tenum.seekCeil(new BytesRef(termText)) == TermsEnum.SeekStatus.END) { + // reached to the end of the iterator + resetTermsIterator(); + log.info("Reached the end of the term iterator for field: {}.", curField); + return Optional.empty(); + } else { + return Optional.of(new Term(curField, tenum.term())); + } + } catch (IOException e) { + resetTermsIterator(); + throw new LukeException(String.format(Locale.ENGLISH, "Terms not available for field: %s.", curField), e); + } finally { + // discard current postings enum + resetPostingsIterator(); + } + } + + @Override + public Optional firstTermDoc() { + if (tenum == null) { + // terms enum is not set + log.warn("Terms enum un-positioned."); + return Optional.empty(); + } + + try { + setPostingsIterator(tenum.postings(penum, PostingsEnum.ALL)); + + if (penum.nextDoc() == PostingsEnum.NO_MORE_DOCS) { + // no docs available for this term + resetPostingsIterator(); + log.warn("No docs available for term: {} in field: {}.", BytesRefUtils.decode(tenum.term()), curField); + return Optional.empty(); + } else { + return Optional.of(penum.docID()); + } + } catch (IOException e) { + resetPostingsIterator(); + throw new LukeException(String.format(Locale.ENGLISH, "Term docs not available for field: %s.", curField), e); + } + } + + @Override + public Optional nextTermDoc() { + if (penum == null) { + // postings enum is not initialized + log.warn("Postings enum un-positioned for field: {}.", curField); + return Optional.empty(); + } + + try { + if (penum.nextDoc() == PostingsEnum.NO_MORE_DOCS) { + // end of the iterator + resetPostingsIterator(); + log.info("Reached the end of the postings iterator for term: {} in field: {}", BytesRefUtils.decode(tenum.term()), curField); + return Optional.empty(); + } else { + return Optional.of(penum.docID()); + } + } catch (IOException e) { + resetPostingsIterator(); + throw new LukeException(String.format(Locale.ENGLISH, "Term docs not available for field: %s.", curField), e); + } + } + + @Override + public List getTermPositions() { + if (penum == null) { + // postings enum is not initialized + log.warn("Postings enum un-positioned for field: {}.", curField); + return Collections.emptyList(); + } + + List res = new ArrayList<>(); + + try { + int freq = penum.freq(); + + for (int i = 0; i < freq; i++) { + int position = penum.nextPosition(); + if (position < 0) { + // no position information available + continue; + } + TermPosting posting = TermPosting.of(position, penum); + res.add(posting); + } + + } catch (IOException e) { + throw new LukeException(String.format(Locale.ENGLISH, "Postings not available for field %s.", curField), e); + } + + return res; + } + + + @Override + public Optional getDocFreq() { + if (tenum == null) { + // terms enum is not initialized + log.warn("Terms enum un-positioned for field: {}.", curField); + return Optional.empty(); + } + + try { + return Optional.of(tenum.docFreq()); + } catch (IOException e) { + throw new LukeException(String.format(Locale.ENGLISH, "Doc frequency not available for field: %s.", curField), e); + } + } + + @Override + public List getTermVectors(int docid, String field) { + try { + return tvAdapter.getTermVector(docid, field); + } catch (IOException e) { + throw new LukeException(String.format(Locale.ENGLISH, "Term vector not available for doc: #%d and field: %s", docid, field), e); + } + } + + @Override + public Optional getDocValues(int docid, String field) { + try { + return dvAdapter.getDocValues(docid, field); + } catch (IOException e) { + throw new LukeException(String.format(Locale.ENGLISH, "Doc values not available for doc: #%d and field: %s", docid, field), e); + } + } + + private void resetCurrentField() { + this.curField = null; + } + + private void setCurrentField(String field) { + this.curField = field; + } + + private void resetTermsIterator() { + this.tenum = null; + } + + private void setTermsIterator(TermsEnum tenum) { + this.tenum = tenum; + } + + private void resetPostingsIterator() { + this.penum = null; + } + + private void setPostingsIterator(PostingsEnum penum) { + this.penum = penum; + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermPosting.java b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermPosting.java new file mode 100644 index 000000000000..84d7af1b264a --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermPosting.java @@ -0,0 +1,90 @@ +/* + * 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.models.documents; + +import java.io.IOException; + +import org.apache.lucene.index.PostingsEnum; +import org.apache.lucene.util.BytesRef; + +/** + * Holder for a term's position information, and optionally, offsets and payloads. + */ +public final class TermPosting { + + // position + private int position = -1; + + // start and end offset (optional) + private int startOffset = -1; + private int endOffset = -1; + + // payload (optional) + private BytesRef payload; + + static TermPosting of(int position, PostingsEnum penum) throws IOException { + TermPosting posting = new TermPosting(); + + // set position + posting.position = position; + + // set offset (if available) + int sOffset = penum.startOffset(); + int eOffset = penum.endOffset(); + if (sOffset >= 0 && eOffset >= 0) { + posting.startOffset = sOffset; + posting.endOffset = eOffset; + } + + // set payload (if available) + if (penum.getPayload() != null) { + posting.payload = BytesRef.deepCopyOf(penum.getPayload()); + } + + return posting; + } + + public int getPosition() { + return position; + } + + public int getStartOffset() { + return startOffset; + } + + public int getEndOffset() { + return endOffset; + } + + public BytesRef getPayload() { + return payload; + } + + @Override + public String toString() { + return "TermPosting{" + + "position=" + position + + ", startOffset=" + startOffset + + ", endOffset=" + endOffset + + ", payload=" + payload + + '}'; + } + + private TermPosting() { + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermVectorEntry.java b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermVectorEntry.java new file mode 100644 index 000000000000..377a58cdaefe --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermVectorEntry.java @@ -0,0 +1,174 @@ +/* + * 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.models.documents; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.OptionalInt; +import java.util.stream.Collectors; + +import org.apache.lucene.index.PostingsEnum; +import org.apache.lucene.index.TermsEnum; +import org.apache.lucene.luke.util.BytesRefUtils; + +/** + * Holder for term vector entry representing the term and their number of occurrences, and optionally, positions in the document field. + */ +public final class TermVectorEntry { + + private final String termText; + private final long freq; + private final List positions; + + /** + * Returns a new term vector entry representing the specified term, and optionally, positions. + * + * @param te - positioned terms iterator + * @return term vector entry + * @throws IOException - if there is a low level IO error. + */ + static TermVectorEntry of(@Nonnull TermsEnum te) throws IOException { + String termText = BytesRefUtils.decode(te.term()); + + List tvPositions = new ArrayList<>(); + PostingsEnum pe = te.postings(null, PostingsEnum.OFFSETS); + pe.nextDoc(); + int freq = pe.freq(); + for (int i = 0; i < freq; i++) { + int pos = pe.nextPosition(); + if (pos < 0) { + // no position information available + continue; + } + TermVectorPosition tvPos = TermVectorPosition.of(pos, pe); + tvPositions.add(tvPos); + } + + return new TermVectorEntry(termText, te.totalTermFreq(), tvPositions); + } + + private TermVectorEntry(String termText, long freq, List positions) { + this.termText = termText; + this.freq = freq; + this.positions = positions; + } + + /** + * Returns the string representation for this term. + */ + public String getTermText() { + return termText; + } + + /** + * Returns the number of occurrences of this term in the document field. + */ + public long getFreq() { + return freq; + } + + /** + * Returns the list of positions for this term in the document field. + */ + public List getPositions() { + return positions; + } + + @Override + public String toString() { + String positionsStr = positions.stream() + .map(TermVectorPosition::toString) + .collect(Collectors.joining(",")); + + return "TermVectorEntry{" + + "termText='" + termText + '\'' + + ", freq=" + freq + + ", positions=" + positionsStr + + '}'; + } + + /** + * Holder for position information for a term vector entry. + */ + public static final class TermVectorPosition { + private final int position; + private final int startOffset; + private final int endOffset; + + /** + * Returns a new position entry representing the specified posting, and optionally, start and end offsets. + * + * @param pos - term position + * @param pe - positioned postings iterator + * @return position entry + * @throws IOException - if there is a low level IO error. + */ + static TermVectorPosition of(int pos, @Nonnull PostingsEnum pe) throws IOException { + int sOffset = pe.startOffset(); + int eOffset = pe.endOffset(); + if (sOffset >= 0 && eOffset >= 0) { + return new TermVectorPosition(pos, sOffset, eOffset); + } + return new TermVectorPosition(pos); + } + + /** + * Returns the position for this term in the document field. + */ + public int getPosition() { + return position; + } + + /** + * Returns the start offset for this term in the document field. + * Empty Optional instance is returned if no offset information available. + */ + public OptionalInt getStartOffset() { + return startOffset >= 0 ? OptionalInt.of(startOffset) : OptionalInt.empty(); + } + + /** + * Returns the end offset for this term in the document field. + * Empty Optional instance is returned if no offset information available. + */ + public OptionalInt getEndOffset() { + return endOffset >= 0 ? OptionalInt.of(endOffset) : OptionalInt.empty(); + } + + @Override + public String toString() { + return "TermVectorPosition{" + + "position=" + position + + ", startOffset=" + startOffset + + ", endOffset=" + endOffset + + '}'; + } + + private TermVectorPosition(int position) { + this(position, -1, -1); + } + + private TermVectorPosition(int position, int startOffset, int endOffset) { + this.position = position; + this.startOffset = startOffset; + this.endOffset = endOffset; + } + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermVectorsAdapter.java b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermVectorsAdapter.java new file mode 100644 index 000000000000..0b4849ba5911 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermVectorsAdapter.java @@ -0,0 +1,71 @@ +/* + * 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.models.documents; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.Terms; +import org.apache.lucene.index.TermsEnum; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An utility class to access to the term vectors. + */ +final class TermVectorsAdapter { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private IndexReader reader; + + TermVectorsAdapter(@Nonnull IndexReader reader) { + this.reader = reader; + } + + /** + * Returns the term vectors for the specified field in the specified document. + * If no term vector is available for the field, empty list is returned. + * + * @param docid - document id + * @param field - field name + * @return list of term vector elements + * @throws IOException - if there is a low level IO error. + */ + List getTermVector(int docid, String field) throws IOException { + Terms termVector = reader.getTermVector(docid, field); + if (termVector == null) { + // no term vector available + log.warn("No term vector indexed for doc: #{} and field: {}", docid, field); + return Collections.emptyList(); + } + + List res = new ArrayList<>(); + TermsEnum te = termVector.iterator(); + while (te.next() != null) { + res.add(TermVectorEntry.of(te)); + } + return res; + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/documents/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/package-info.java new file mode 100644 index 000000000000..6f4a38b753c0 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/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. + */ + +/** Models and APIs for the Documents tab */ +package org.apache.lucene.luke.models.documents; \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/overview/Overview.java b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/Overview.java new file mode 100644 index 000000000000..54095cb63200 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/Overview.java @@ -0,0 +1,121 @@ +/* + * 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.models.overview; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * A dedicated interface for Luke's Overview tab. + */ +public interface Overview { + + /** + * Returns the currently opened index directory path, + * or the root directory path if multiple index directories are opened. + */ + String getIndexPath(); + + /** + * Returns the number of fields in this index. + */ + int getNumFields(); + + /** + * Returns the number of documents in this index. + */ + int getNumDocuments(); + + /** + * Returns the total number of terms in this index. + * + * @throws org.apache.lucene.luke.models.LukeException - if an internal error occurs when accessing index + */ + long getNumTerms(); + + /** + * Returns true if this index includes deleted documents. + */ + boolean hasDeletions(); + + /** + * Returns the number of deleted documents in this index. + */ + int getNumDeletedDocs(); + + /** + * Returns true if the index is optimized. + * Empty Optional instance is returned if multiple indexes are opened. + */ + Optional isOptimized(); + + /** + * Returns the version number when this index was opened. + * Empty Optional instance is returned if multiple indexes are opened. + */ + Optional getIndexVersion(); + + /** + * Returns the string representation for the Lucene segment version when the index was created. + * Empty Optional instance is returned if multiple indexes are opened. + * + * @throws org.apache.lucene.luke.models.LukeException - if an internal error occurs when accessing index + */ + Optional getIndexFormat(); + + /** + * Returns the currently opened {@link org.apache.lucene.store.Directory} implementation class name. + * Empty Optional instance is returned if multiple indexes are opened. + */ + Optional getDirImpl(); + + /** + * Returns the information of the commit point that reader has opened. + *

+ * Empty Optional instance is returned if multiple indexes are opened. + */ + Optional getCommitDescription(); + + /** + * Returns the user provided data for the commit point. + * Empty Optional instance is returned if multiple indexes are opened. + * + * @throws org.apache.lucene.luke.models.LukeException - if an internal error occurs when accessing index + */ + Optional getCommitUserData(); + + /** + * Returns all fields with the number of terms for each field sorted by {@link TermCountsOrder} + * + * @param order - the sort order + * @return the ordered map of terms and their frequencies + * @throws org.apache.lucene.luke.models.LukeException - if an internal error occurs when accessing index + */ + Map getSortedTermCounts(TermCountsOrder order); + + /** + * Returns the top indexed terms with their statistics for the specified field. + * + * @param field - the field name + * @param numTerms - the max number of terms to be returned + * @return the list of top terms and their document frequencies + * @throws org.apache.lucene.luke.models.LukeException - if an internal error occurs when accessing index + */ + List getTopTerms(String field, int numTerms); +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/overview/OverviewFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/OverviewFactory.java new file mode 100644 index 000000000000..620e2e51501a --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/OverviewFactory.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.models.overview; + +import org.apache.lucene.index.IndexReader; + +/** Factory of {@link Overview} */ +public class OverviewFactory { + + public Overview newInstance(IndexReader reader, String indexPath) { + return new OverviewImpl(reader, indexPath); + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/overview/OverviewImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/OverviewImpl.java new file mode 100644 index 000000000000..4d12b5bdfa43 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/OverviewImpl.java @@ -0,0 +1,170 @@ +/* + * 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.models.overview; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.luke.models.LukeException; +import org.apache.lucene.luke.models.LukeModel; +import org.apache.lucene.luke.models.util.IndexUtils; + +/** Default implementation of {@link Overview} */ +public final class OverviewImpl extends LukeModel implements Overview { + + private final String indexPath; + + private final TermCounts termCounts; + + private final TopTerms topTerms; + + /** + * Constructs an OverviewImpl that holds the given {@link IndexReader}. + * + * @param reader - the index reader + * @param indexPath - the (root) index directory path + * @throws LukeException - if an internal error is occurred when accessing index + */ + public OverviewImpl(@Nonnull IndexReader reader, @Nonnull String indexPath) { + super(reader); + this.indexPath = indexPath; + try { + this.termCounts = new TermCounts(reader); + } catch (IOException e) { + throw new LukeException("An error occurred when collecting term statistics."); + } + this.topTerms = new TopTerms(reader); + } + + @Override + public String getIndexPath() { + return indexPath; + } + + @Override + public int getNumFields() { + return IndexUtils.getFieldInfos(reader).size(); + } + + @Override + public int getNumDocuments() { + return reader.numDocs(); + } + + @Override + public long getNumTerms() { + return termCounts.numTerms(); + } + + @Override + public boolean hasDeletions() { + return reader.hasDeletions(); + } + + @Override + public int getNumDeletedDocs() { + return reader.numDeletedDocs(); + } + + @Override + public Optional isOptimized() { + if (commit != null) { + return Optional.of(commit.getSegmentCount() == 1); + } + return Optional.empty(); + } + + @Override + public Optional getIndexVersion() { + if (reader instanceof DirectoryReader) { + return Optional.of(((DirectoryReader) reader).getVersion()); + } + return Optional.empty(); + } + + @Override + public Optional getIndexFormat() { + if (dir == null) { + return Optional.empty(); + } + try { + return Optional.of(IndexUtils.getIndexFormat(dir)); + } catch (IOException e) { + throw new LukeException("Index format not available.", e); + } + } + + @Override + public Optional getDirImpl() { + if (dir == null) { + return Optional.empty(); + } + return Optional.of(dir.getClass().getName()); + } + + @Override + public Optional getCommitDescription() { + if (commit == null) { + return Optional.empty(); + } + return Optional.of( + commit.getSegmentsFileName() + + " (generation=" + commit.getGeneration() + + ", segs=" + commit.getSegmentCount() + ")"); + } + + @Override + public Optional getCommitUserData() { + if (commit == null) { + return Optional.empty(); + } + try { + return Optional.of(IndexUtils.getCommitUserData(commit)); + } catch (IOException e) { + throw new LukeException("Commit user data not available.", e); + } + } + + @Override + public Map getSortedTermCounts(@Nullable TermCountsOrder order) { + if (order == null) { + order = TermCountsOrder.COUNT_DESC; + } + return termCounts.sortedTermCounts(order); + } + + @Override + public List getTopTerms(@Nonnull String field, int numTerms) { + if (numTerms < 0) { + throw new IllegalArgumentException(String.format(Locale.ENGLISH, "'numTerms' must be a positive integer: %d is not accepted.", numTerms)); + } + try { + return topTerms.getTopTerms(field, numTerms); + } catch (Exception e) { + throw new LukeException(String.format(Locale.ENGLISH, "Top terms for field %s not available.", field), e); + } + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermCounts.java b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermCounts.java new file mode 100644 index 000000000000..2b7e43d30b77 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermCounts.java @@ -0,0 +1,80 @@ +/* + * 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.models.overview; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.luke.models.util.IndexUtils; + +/** + * An utility class that collects term counts terms for all fields in a index. + */ +final class TermCounts { + + private final Map termCountMap; + + TermCounts(@Nonnull IndexReader reader) throws IOException { + termCountMap = IndexUtils.countTerms(reader, IndexUtils.getFieldNames(reader)); + } + + /** + * Returns the total number of terms in this index. + */ + long numTerms() { + return termCountMap.values().stream().mapToLong(Long::longValue).sum(); + } + + /** + * Returns all fields with the number of terms for each field sorted by {@link TermCountsOrder} + * + * @param order - sort order + */ + Map sortedTermCounts(@Nonnull TermCountsOrder order) { + Comparator> comparator; + switch (order) { + case NAME_ASC: + comparator = Map.Entry.comparingByKey(); + break; + case NAME_DESC: + comparator = Map.Entry.comparingByKey().reversed(); + break; + case COUNT_ASC: + comparator = Map.Entry.comparingByValue(); + break; + case COUNT_DESC: + comparator = Map.Entry.comparingByValue().reversed(); + break; + default: + comparator = Map.Entry.comparingByKey(); + } + return sortedTermCounts(comparator); + } + + private Map sortedTermCounts(Comparator> comparator) { + return termCountMap.entrySet().stream() + .sorted(comparator) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1, LinkedHashMap::new)); + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermCountsOrder.java b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermCountsOrder.java new file mode 100644 index 000000000000..a5976ba8d529 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermCountsOrder.java @@ -0,0 +1,43 @@ +/* + * 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.models.overview; + +/** + * Sort orders for fields with their term counts + */ +public enum TermCountsOrder { + /** + * Ascending order by the field name + */ + NAME_ASC, + + /** + * Descending order by the field name + */ + NAME_DESC, + + /** + * Ascending order by the count of terms + */ + COUNT_ASC, + + /** + * Descending order by the count of terms + */ + COUNT_DESC +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermStats.java b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermStats.java new file mode 100644 index 000000000000..b97afe7c0ae5 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermStats.java @@ -0,0 +1,76 @@ +/* + * 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.models.overview; + +import org.apache.lucene.luke.util.BytesRefUtils; + +/** + * Holder for statistics for a term in a specific field. + */ +public final class TermStats { + + private final String decodedTermText; + + private final String field; + + private final int docFreq; + + /** + * Returns a TermStats instance representing the specified {@link org.apache.lucene.misc.TermStats} value. + */ + static TermStats of(org.apache.lucene.misc.TermStats stats) { + String termText = BytesRefUtils.decode(stats.termtext); + return new TermStats(termText, stats.field, stats.docFreq); + } + + private TermStats(String decodedTermText, String field, int docFreq) { + this.decodedTermText = decodedTermText; + this.field = field; + this.docFreq = docFreq; + } + + /** + * Returns the string representation for this term. + */ + public String getDecodedTermText() { + return decodedTermText; + } + + /** + * Returns the field name. + */ + public String getField() { + return field; + } + + /** + * Returns the document frequency of this term. + */ + public int getDocFreq() { + return docFreq; + } + + @Override + public String toString() { + return "TermStats{" + + "decodedTermText='" + decodedTermText + '\'' + + ", field='" + field + '\'' + + ", docFreq=" + docFreq + + '}'; + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TopTerms.java b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TopTerms.java new file mode 100644 index 000000000000..7b98d91f8a2f --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TopTerms.java @@ -0,0 +1,68 @@ +/* + * 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.models.overview; + +import javax.annotation.Nonnull; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableList; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.misc.HighFreqTerms; + +/** + * An utility class that collects terms and their statistics in a specific field. + */ +final class TopTerms { + + private final IndexReader reader; + + private final Map> topTermsCache; + + TopTerms(@Nonnull IndexReader reader) { + this.reader = reader; + this.topTermsCache = new WeakHashMap<>(); + } + + /** + * Returns the top indexed terms with their statistics for the specified field. + * + * @param field - the field name + * @param numTerms - the max number of terms to be returned + * @throws Exception - if an error occurs when collecting term statistics + */ + List getTopTerms(String field, int numTerms) throws Exception { + + if (!topTermsCache.containsKey(field) || topTermsCache.get(field).size() < numTerms) { + org.apache.lucene.misc.TermStats[] stats = + HighFreqTerms.getHighFreqTerms(reader, numTerms, field, new HighFreqTerms.DocFreqComparator()); + + List topTerms = Arrays.stream(stats) + .map(TermStats::of) + .collect(Collectors.toList()); + + // cache computed statistics for later uses + topTermsCache.put(field, topTerms); + } + + return ImmutableList.copyOf(topTermsCache.get(field)); + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/overview/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/package-info.java new file mode 100644 index 000000000000..11b12e81266e --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/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. + */ + +/** Models and APIs for the Overview tab */ +package org.apache.lucene.luke.models.overview; \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/models/package-info.java new file mode 100644 index 000000000000..0065130864b4 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/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. + */ + +/** Models and internal APIs for Luke */ +package org.apache.lucene.luke.models; \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/search/MLTConfig.java b/lucene/luke/src/java/org/apache/lucene/luke/models/search/MLTConfig.java new file mode 100644 index 000000000000..e1bdf50e01f7 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/search/MLTConfig.java @@ -0,0 +1,96 @@ +/* + * 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.models.search; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.google.common.collect.ImmutableList; +import org.apache.lucene.queries.mlt.MoreLikeThis; + +/** + * Configurations for MoreLikeThis query. + */ +public final class MLTConfig { + + private final List fields; + + private final int maxDocFreq; + + private final int minDocFreq; + + private final int minTermFreq; + + /** {@link MLTConfig} builder */ + public static class Builder { + + private final List fields = new ArrayList<>(); + private int maxDocFreq = MoreLikeThis.DEFAULT_MAX_DOC_FREQ; + private int minDocFreq = MoreLikeThis.DEFAULT_MIN_DOC_FREQ; + private int minTermFreq = MoreLikeThis.DEFAULT_MIN_TERM_FREQ; + + public Builder fields(Collection val) { + fields.addAll(val); + return this; + } + + public Builder maxDocFreq(int val) { + maxDocFreq = val; + return this; + } + + public Builder minDocFreq(int val) { + minDocFreq = val; + return this; + } + + public Builder minTermFreq(int val) { + minTermFreq = val; + return this; + } + + public MLTConfig build() { + return new MLTConfig(this); + } + } + + private MLTConfig(Builder builder) { + this.fields = ImmutableList.copyOf(builder.fields); + this.maxDocFreq = builder.maxDocFreq; + this.minDocFreq = builder.minDocFreq; + this.minTermFreq = builder.minTermFreq; + } + + public String[] getFieldNames() { + return fields.toArray(new String[fields.size()]); + } + + public int getMaxDocFreq() { + return maxDocFreq; + } + + public int getMinDocFreq() { + return minDocFreq; + } + + public int getMinTermFreq() { + return minTermFreq; + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/search/QueryParserConfig.java b/lucene/luke/src/java/org/apache/lucene/luke/models/search/QueryParserConfig.java new file mode 100644 index 000000000000..c5bf0548b595 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/search/QueryParserConfig.java @@ -0,0 +1,251 @@ +/* + * 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.models.search; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableMap; +import org.apache.lucene.document.DateTools; + +/** + * Configurations for query parser. + */ +public final class QueryParserConfig { + + /** (Default) operator */ + public enum Operator { + AND, OR + } + + private final boolean useClassicParser; + + private final boolean enablePositionIncrements; + + private final boolean allowLeadingWildcard; + + private final DateTools.Resolution dateResolution; + + private final Operator defaultOperator; + + private final float fuzzyMinSim; + + private final int fuzzyPrefixLength; + + private final Locale locale; + + private final TimeZone timeZone; + + private final int phraseSlop; + + // classic parser only configurations + private final boolean autoGenerateMultiTermSynonymsPhraseQuery; + + private final boolean autoGeneratePhraseQueries; + + private final boolean splitOnWhitespace; + + // standard parser only configurations + private final Map> typeMap; + + /** {@link QueryParserConfig} builder */ + public static class Builder { + private boolean useClassicParser = true; + private boolean enablePositionIncrements = true; + private boolean allowLeadingWildcard = false; + private DateTools.Resolution dateResolution = DateTools.Resolution.MILLISECOND; + private Operator defaultOperator = Operator.OR; + private float fuzzyMinSim = 2f; + private int fuzzyPrefixLength = 0; + private Locale locale = Locale.getDefault(); + private TimeZone timeZone = TimeZone.getDefault(); + private int phraseSlop = 0; + private boolean autoGenerateMultiTermSynonymsPhraseQuery = false; + private boolean autoGeneratePhraseQueries = false; + private boolean splitOnWhitespace = false; + private Map> typeMap = new HashMap<>(); + + public Builder useClassicParser(boolean value) { + useClassicParser = value; + return this; + } + + public Builder enablePositionIncrements(boolean value) { + enablePositionIncrements = value; + return this; + } + + public Builder allowLeadingWildcard(boolean value) { + allowLeadingWildcard = value; + return this; + } + + public Builder dateResolution(DateTools.Resolution value) { + dateResolution = value; + return this; + } + + public Builder defaultOperator(Operator op) { + defaultOperator = op; + return this; + } + + public Builder fuzzyMinSim(float val) { + fuzzyMinSim = val; + return this; + } + + public Builder fuzzyPrefixLength(int val) { + fuzzyPrefixLength = val; + return this; + } + + public Builder locale(Locale val) { + locale = val; + return this; + } + + public Builder timeZone(TimeZone val) { + timeZone = val; + return this; + } + + public Builder phraseSlop(int val) { + phraseSlop = val; + return this; + } + + public Builder autoGenerateMultiTermSynonymsPhraseQuery(boolean val) { + autoGenerateMultiTermSynonymsPhraseQuery = val; + return this; + } + + public Builder autoGeneratePhraseQueries(boolean val) { + autoGeneratePhraseQueries = val; + return this; + } + + public Builder splitOnWhitespace(boolean val) { + splitOnWhitespace = val; + return this; + } + + public Builder typeMap(Map> val) { + typeMap = val; + return this; + } + + public QueryParserConfig build() { + return new QueryParserConfig(this); + } + } + + private QueryParserConfig(Builder builder) { + this.useClassicParser = builder.useClassicParser; + this.enablePositionIncrements = builder.enablePositionIncrements; + this.allowLeadingWildcard = builder.allowLeadingWildcard; + this.dateResolution = builder.dateResolution; + this.defaultOperator = builder.defaultOperator; + this.fuzzyMinSim = builder.fuzzyMinSim; + this.fuzzyPrefixLength = builder.fuzzyPrefixLength; + this.locale = builder.locale; + this.timeZone = builder.timeZone; + this.phraseSlop = builder.phraseSlop; + this.autoGenerateMultiTermSynonymsPhraseQuery = builder.autoGenerateMultiTermSynonymsPhraseQuery; + this.autoGeneratePhraseQueries = builder.autoGeneratePhraseQueries; + this.splitOnWhitespace = builder.splitOnWhitespace; + this.typeMap = ImmutableMap.copyOf(builder.typeMap); + } + + public boolean isUseClassicParser() { + return useClassicParser; + } + + public boolean isAutoGenerateMultiTermSynonymsPhraseQuery() { + return autoGenerateMultiTermSynonymsPhraseQuery; + } + + public boolean isEnablePositionIncrements() { + return enablePositionIncrements; + } + + public boolean isAllowLeadingWildcard() { + return allowLeadingWildcard; + } + + public boolean isAutoGeneratePhraseQueries() { + return autoGeneratePhraseQueries; + } + + public boolean isSplitOnWhitespace() { + return splitOnWhitespace; + } + + public DateTools.Resolution getDateResolution() { + return dateResolution; + } + + public Operator getDefaultOperator() { + return defaultOperator; + } + + public float getFuzzyMinSim() { + return fuzzyMinSim; + } + + public int getFuzzyPrefixLength() { + return fuzzyPrefixLength; + } + + public Locale getLocale() { + return locale; + } + + public TimeZone getTimeZone() { + return timeZone; + } + + public int getPhraseSlop() { + return phraseSlop; + } + + public Map> getTypeMap() { + return typeMap; + } + + @Override + public String toString() { + return "QueryParserConfig: [" + + " default operator=" + defaultOperator.name() + ";" + + " enable position increment=" + enablePositionIncrements + ";" + + " allow leading wildcard=" + allowLeadingWildcard + ";" + + " split whitespace=" + splitOnWhitespace + ";" + + " generate phrase query=" + autoGeneratePhraseQueries + ";" + + " generate multiterm sysnonymsphrase query=" + autoGenerateMultiTermSynonymsPhraseQuery + ";" + + " phrase slop=" + phraseSlop + ";" + + " date resolution=" + dateResolution.name() + + " locale=" + locale.toLanguageTag() + ";" + + " time zone=" + timeZone.getID() + ";" + + " numeric types=" + String.join(",", getTypeMap().entrySet().stream() + .map(e -> e.getKey() + "=" + e.getValue().toString()).collect(Collectors.toSet())) + ";" + + "]"; + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/search/Search.java b/lucene/luke/src/java/org/apache/lucene/luke/models/search/Search.java new file mode 100644 index 000000000000..52eea8fabd2b --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/search/Search.java @@ -0,0 +1,156 @@ +/* + * 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.models.search; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.luke.models.LukeException; +import org.apache.lucene.search.Explanation; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.Sort; +import org.apache.lucene.search.SortField; + +/** + * A dedicated interface for Luke's Search tab. + */ +public interface Search { + + /** + * Returns all field names in this index. + */ + Collection getFieldNames(); + + /** + * Returns field names those are sortable. + */ + Collection getSortableFieldNames(); + + /** + * Returns field names those are searchable. + */ + Collection getSearchableFieldNames(); + + /** + * Returns field names those are searchable by range query. + */ + Collection getRangeSearchableFieldNames(); + + /** + * Returns the current query. + */ + Query getCurrentQuery(); + + /** + * Parses the specified query expression with given configurations. + * + * @param expression - query expression + * @param defField - default field for the query + * @param analyzer - analyzer for parsing query expression + * @param config - query parser configuration + * @param rewrite - if true, re-written query is returned + * @return parsed query + * @throws LukeException - if an internal error occurs when accessing index + */ + Query parseQuery(String expression, String defField, Analyzer analyzer, QueryParserConfig config, boolean rewrite); + + /** + * Creates the MoreLikeThis query for the specified document with given configurations. + * + * @param docid - document id + * @param mltConfig - MoreLikeThis configuration + * @param analyzer - analyzer for analyzing the document + * @return MoreLikeThis query + * @throws LukeException - if an internal error occurs when accessing index + */ + Query mltQuery(int docid, MLTConfig mltConfig, Analyzer analyzer); + + /** + * Searches this index by the query with given configurations. + * + * @param query - search query + * @param simConfig - similarity configuration + * @param fieldsToLoad - field names to load + * @param pageSize - page size + * @return search results + * @throws LukeException - if an internal error occurs when accessing index + */ + SearchResults search(Query query, SimilarityConfig simConfig, Set fieldsToLoad, int pageSize); + + /** + * Searches this index by the query with given sort criteria and configurations. + * + * @param query - search query + * @param simConfig - similarity configuration + * @param sort - sort criteria + * @param fieldsToLoad - fields to load + * @param pageSize - page size + * @return search results + * @throws LukeException - if an internal error occurs when accessing index + */ + SearchResults search(Query query, SimilarityConfig simConfig, Sort sort, Set fieldsToLoad, int pageSize); + + /** + * Returns the next page for the current query. + * + * @return search results, or empty if there are no more results + * @throws LukeException - if an internal error occurs when accessing index + */ + Optional nextPage(); + + /** + * Returns the previous page for the current query. + * + * @return search results, or empty if there are no more results. + * @throws LukeException - if an internal error occurs when accessing index + */ + Optional prevPage(); + + /** + * Explains the document for the specified query. + * + * @param query - query + * @param docid - document id to be explained + * @return explanations + * @throws LukeException - if an internal error occurs when accessing index + */ + Explanation explain(Query query, int docid); + + /** + * Returns possible {@link SortField}s for the specified field. + * + * @param name - field name + * @return list of possible sort types + * @throws LukeException - if an internal error occurs when accessing index + */ + List guessSortTypes(String name); + + /** + * Returns the {@link SortField} for the specified field with the sort type and order. + * + * @param name - field name + * @param type - string representation for a type + * @param reverse - if true, descending order is used + * @return sort type, or empty if the type is incompatible with the field + * @throws LukeException - if an internal error occurs when accessing index + */ + Optional getSortType(String name, String type, boolean reverse); +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchFactory.java new file mode 100644 index 000000000000..b2f97b11e6ae --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchFactory.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.models.search; + +import org.apache.lucene.index.IndexReader; + +/** Factory of {@link Search} */ +public class SearchFactory { + + public Search newInstance(IndexReader reader) { + return new SearchImpl(reader); + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchImpl.java new file mode 100644 index 000000000000..9a9e7c44e14b --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchImpl.java @@ -0,0 +1,451 @@ +/* + * 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.models.search; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.text.NumberFormat; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.index.DocValuesType; +import org.apache.lucene.index.FieldInfo; +import org.apache.lucene.index.IndexOptions; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.luke.models.LukeException; +import org.apache.lucene.luke.models.LukeModel; +import org.apache.lucene.luke.models.util.IndexUtils; +import org.apache.lucene.queries.mlt.MoreLikeThis; +import org.apache.lucene.queryparser.classic.ParseException; +import org.apache.lucene.queryparser.classic.QueryParser; +import org.apache.lucene.queryparser.flexible.core.QueryNodeException; +import org.apache.lucene.queryparser.flexible.standard.StandardQueryParser; +import org.apache.lucene.queryparser.flexible.standard.config.PointsConfig; +import org.apache.lucene.queryparser.flexible.standard.config.StandardQueryConfigHandler; +import org.apache.lucene.search.Explanation; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.Sort; +import org.apache.lucene.search.SortField; +import org.apache.lucene.search.SortedNumericSortField; +import org.apache.lucene.search.SortedSetSortField; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.search.similarities.BM25Similarity; +import org.apache.lucene.search.similarities.ClassicSimilarity; +import org.apache.lucene.search.similarities.Similarity; +import org.apache.lucene.util.ArrayUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Default implementation of {@link Search} */ +public final class SearchImpl extends LukeModel implements Search { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private static final int DEFAULT_PAGE_SIZE = 10; + + private final IndexSearcher searcher; + + private int pageSize = DEFAULT_PAGE_SIZE; + + private int currentPage = -1; + + private long totalHits = -1; + + private ScoreDoc[] docs = new ScoreDoc[0]; + + private Query query; + + private Sort sort; + + private Set fieldsToLoad; + + /** + * Constructs a SearchImpl that holds given {@link IndexReader} + * + * @param reader - the index reader + */ + public SearchImpl(@Nonnull IndexReader reader) { + super(reader); + this.searcher = new IndexSearcher(reader); + } + + @Override + public Collection getSortableFieldNames() { + return IndexUtils.getFieldNames(reader).stream() + .map(f -> IndexUtils.getFieldInfo(reader, f)) + .filter(info -> !info.getDocValuesType().equals(DocValuesType.NONE)) + .map(info -> info.name) + .collect(Collectors.toList()); + } + + @Override + public Collection getSearchableFieldNames() { + return IndexUtils.getFieldNames(reader).stream() + .map(f -> IndexUtils.getFieldInfo(reader, f)) + .filter(info -> !info.getIndexOptions().equals(IndexOptions.NONE)) + .map(info -> info.name) + .collect(Collectors.toList()); + } + + @Override + public Collection getRangeSearchableFieldNames() { + return IndexUtils.getFieldNames(reader).stream() + .map(f -> IndexUtils.getFieldInfo(reader, f)) + //.filter(info -> info.getPointDimensionCount() > 0) + .filter(info -> info.getPointDataDimensionCount() > 0) + .map(info -> info.name) + .collect(Collectors.toSet()); + } + + @Override + public Query getCurrentQuery() { + return this.query; + } + + @Override + public Query parseQuery(@Nonnull String expression, @Nonnull String defField, @Nonnull Analyzer analyzer, + @Nonnull QueryParserConfig config, boolean rewrite) { + + Query query = config.isUseClassicParser() ? + parseByClassicParser(expression, defField, analyzer, config) : + parseByStandardParser(expression, defField, analyzer, config); + + if (rewrite) { + try { + query = query.rewrite(reader); + } catch (IOException e) { + throw new LukeException(String.format(Locale.ENGLISH, "Failed to rewrite query: %s", query.toString()), e); + } + } + + return query; + } + + private Query parseByClassicParser(@Nonnull String expression, @Nonnull String defField, @Nonnull Analyzer analyzer, + @Nonnull QueryParserConfig config) { + QueryParser parser = new QueryParser(defField, analyzer); + + switch (config.getDefaultOperator()) { + case OR: + parser.setDefaultOperator(QueryParser.Operator.OR); + break; + case AND: + parser.setDefaultOperator(QueryParser.Operator.AND); + break; + } + + parser.setSplitOnWhitespace(config.isSplitOnWhitespace()); + parser.setAutoGenerateMultiTermSynonymsPhraseQuery(config.isAutoGenerateMultiTermSynonymsPhraseQuery()); + parser.setAutoGeneratePhraseQueries(config.isAutoGeneratePhraseQueries()); + parser.setEnablePositionIncrements(config.isEnablePositionIncrements()); + parser.setAllowLeadingWildcard(config.isAllowLeadingWildcard()); + parser.setDateResolution(config.getDateResolution()); + parser.setFuzzyMinSim(config.getFuzzyMinSim()); + parser.setFuzzyPrefixLength(config.getFuzzyPrefixLength()); + parser.setLocale(config.getLocale()); + parser.setTimeZone(config.getTimeZone()); + parser.setPhraseSlop(config.getPhraseSlop()); + + try { + return parser.parse(expression); + } catch (ParseException e) { + throw new LukeException(String.format(Locale.ENGLISH, "Failed to parse query expression: %s", expression), e); + } + + } + + private Query parseByStandardParser(@Nonnull String expression, @Nonnull String defField, @Nonnull Analyzer analyzer, + @Nonnull QueryParserConfig config) { + StandardQueryParser parser = new StandardQueryParser(analyzer); + + switch (config.getDefaultOperator()) { + case OR: + parser.setDefaultOperator(StandardQueryConfigHandler.Operator.OR); + break; + case AND: + parser.setDefaultOperator(StandardQueryConfigHandler.Operator.AND); + break; + } + + parser.setEnablePositionIncrements(config.isEnablePositionIncrements()); + parser.setAllowLeadingWildcard(config.isAllowLeadingWildcard()); + parser.setDateResolution(config.getDateResolution()); + parser.setFuzzyMinSim(config.getFuzzyMinSim()); + parser.setFuzzyPrefixLength(config.getFuzzyPrefixLength()); + parser.setLocale(config.getLocale()); + parser.setTimeZone(config.getTimeZone()); + parser.setPhraseSlop(config.getPhraseSlop()); + + if (config.getTypeMap() != null) { + Map pointsConfigMap = new HashMap<>(); + + for (Map.Entry> entry : config.getTypeMap().entrySet()) { + String field = entry.getKey(); + Class type = entry.getValue(); + PointsConfig pc; + if (type == Integer.class || type == Long.class) { + pc = new PointsConfig(NumberFormat.getIntegerInstance(Locale.ROOT), type); + } else if (type == Float.class || type == Double.class) { + pc = new PointsConfig(NumberFormat.getNumberInstance(Locale.ROOT), type); + } else { + log.warn(String.format(Locale.ENGLISH, "Ignored invalid number type: %s.", type.getName())); + continue; + } + pointsConfigMap.put(field, pc); + } + + parser.setPointsConfigMap(pointsConfigMap); + } + + try { + return parser.parse(expression, defField); + } catch (QueryNodeException e) { + throw new LukeException(String.format(Locale.ENGLISH, "Failed to parse query expression: %s", expression), e); + } + + } + + @Override + public Query mltQuery(int docid, MLTConfig mltConfig, Analyzer analyzer) { + MoreLikeThis mlt = new MoreLikeThis(reader); + + mlt.setAnalyzer(analyzer); + mlt.setFieldNames(mltConfig.getFieldNames()); + mlt.setMinDocFreq(mltConfig.getMinDocFreq()); + mlt.setMaxDocFreq(mltConfig.getMaxDocFreq()); + mlt.setMinTermFreq(mltConfig.getMinTermFreq()); + + try { + return mlt.like(docid); + } catch (IOException e) { + throw new LukeException("Failed to create MLT query for doc: " + docid); + } + } + + @Override + public SearchResults search( + @Nonnull Query query, @Nonnull SimilarityConfig simConfig, @Nullable Set fieldsToLoad, int pageSize) { + return search(query, simConfig, null, fieldsToLoad, pageSize); + } + + @Override + public SearchResults search( + @Nonnull Query query, @Nonnull SimilarityConfig simConfig, @Nullable Sort sort, @Nullable Set fieldsToLoad, int pageSize) { + if (pageSize < 0) { + throw new LukeException(new IllegalArgumentException("Negative integer is not acceptable for page size.")); + } + + // reset internal status to prepare for a new search session + this.docs = new ScoreDoc[0]; + this.currentPage = 0; + this.pageSize = pageSize; + this.query = query; + this.sort = sort; + this.fieldsToLoad = fieldsToLoad == null ? null : ImmutableSet.copyOf(fieldsToLoad); + searcher.setSimilarity(createSimilarity(simConfig)); + + try { + return search(); + } catch (IOException e) { + throw new LukeException("Search Failed.", e); + } + } + + private SearchResults search() throws IOException { + // execute search + ScoreDoc after = docs.length == 0 ? null : docs[docs.length - 1]; + TopDocs topDocs = sort == null ? + searcher.searchAfter(after, query, pageSize) : + searcher.searchAfter(after, query, pageSize, sort); + + // reset total hits for the current query + this.totalHits = topDocs.totalHits.value; + + // cache search results for later use + ScoreDoc[] newDocs = new ScoreDoc[docs.length + topDocs.scoreDocs.length]; + System.arraycopy(docs, 0, newDocs, 0, docs.length); + System.arraycopy(topDocs.scoreDocs, 0, newDocs, docs.length, topDocs.scoreDocs.length); + this.docs = newDocs; + + return SearchResults.of(topDocs.totalHits.value, topDocs.scoreDocs, currentPage * pageSize, searcher, fieldsToLoad); + } + + @Override + public Optional nextPage() { + if (currentPage < 0 || query == null) { + throw new LukeException(new IllegalStateException("Search session not started.")); + } + + // proceed to next page + currentPage += 1; + + if (totalHits == 0 || currentPage * pageSize >= totalHits) { + log.warn("No more next search results are available."); + return Optional.empty(); + } + + try { + + if (currentPage * pageSize < docs.length) { + // if cached results exist, return that. + int from = currentPage * pageSize; + int to = Math.min(from + pageSize, docs.length); + ScoreDoc[] part = ArrayUtil.copyOfSubArray(docs, from, to); + return Optional.of(SearchResults.of(totalHits, part, from, searcher, fieldsToLoad)); + } else { + return Optional.of(search()); + } + + } catch (IOException e) { + throw new LukeException("Search Failed.", e); + } + } + + + @Override + public Optional prevPage() { + if (currentPage < 0 || query == null) { + throw new LukeException(new IllegalStateException("Search session not started.")); + } + + // return to previous page + currentPage -= 1; + + if (currentPage < 0) { + log.warn("No more previous search results are available."); + return Optional.empty(); + } + + try { + // there should be cached results for this page + int from = currentPage * pageSize; + int to = Math.min(from + pageSize, docs.length); + ScoreDoc[] part = ArrayUtil.copyOfSubArray(docs, from, to); + return Optional.of(SearchResults.of(totalHits, part, from, searcher, fieldsToLoad)); + } catch (IOException e) { + throw new LukeException("Search Failed.", e); + } + } + + private Similarity createSimilarity(@Nonnull SimilarityConfig config) { + Similarity similarity; + + if (config.isUseClassicSimilarity()) { + ClassicSimilarity tfidf = new ClassicSimilarity(); + tfidf.setDiscountOverlaps(config.isDiscountOverlaps()); + similarity = tfidf; + } else { + BM25Similarity bm25 = new BM25Similarity(config.getK1(), config.getB()); + bm25.setDiscountOverlaps(config.isDiscountOverlaps()); + similarity = bm25; + } + + return similarity; + } + + @Override + public List guessSortTypes(String name) { + FieldInfo finfo = IndexUtils.getFieldInfo(reader, name); + if (finfo == null) { + throw new LukeException("No such field: " + name, new IllegalArgumentException()); + } + + DocValuesType dvType = finfo.getDocValuesType(); + + switch (dvType) { + case NONE: + return Collections.emptyList(); + + case NUMERIC: + return Lists.newArrayList( + new SortField(name, SortField.Type.INT), + new SortField(name, SortField.Type.LONG), + new SortField(name, SortField.Type.FLOAT), + new SortField(name, SortField.Type.DOUBLE)); + + case SORTED_NUMERIC: + return Lists.newArrayList( + new SortedNumericSortField(name, SortField.Type.INT), + new SortedNumericSortField(name, SortField.Type.LONG), + new SortedNumericSortField(name, SortField.Type.FLOAT), + new SortedNumericSortField(name, SortField.Type.DOUBLE)); + + case SORTED: + return Lists.newArrayList( + new SortField(name, SortField.Type.STRING), + new SortField(name, SortField.Type.STRING_VAL)); + + case SORTED_SET: + return Collections.singletonList(new SortedSetSortField(name, false)); + + default: + return Collections.singletonList(new SortField(name, SortField.Type.DOC)); + } + + } + + @Override + public Optional getSortType(@Nonnull String name, @Nonnull String type, boolean reverse) { + List candidates = guessSortTypes(name); + if (candidates.isEmpty()) { + log.warn(String.format(Locale.ENGLISH, "No available sort types for: %s", name)); + return Optional.empty(); + } + + // TODO should be refactored... + for (SortField sf : candidates) { + if (sf instanceof SortedSetSortField) { + return Optional.of(new SortedSetSortField(sf.getField(), reverse)); + } else if (sf instanceof SortedNumericSortField) { + SortField.Type sfType = ((SortedNumericSortField) sf).getNumericType(); + if (sfType.name().equals(type)) { + return Optional.of(new SortedNumericSortField(sf.getField(), sfType, reverse)); + } + } else { + SortField.Type sfType = sf.getType(); + if (sfType.name().equals(type)) { + return Optional.of(new SortField(sf.getField(), sfType, reverse)); + } + } + } + return Optional.empty(); + } + + @Override + public Explanation explain(Query query, int docid) { + try { + return searcher.explain(query, docid); + } catch (IOException e) { + throw new LukeException(String.format(Locale.ENGLISH, "Failed to create explanation for doc: %d for query: \"%s\"", docid, query.toString()), e); + } + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchResults.java b/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchResults.java new file mode 100644 index 000000000000..f63d3983d02c --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchResults.java @@ -0,0 +1,157 @@ +/* + * 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.models.search; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import org.apache.lucene.document.Document; +import org.apache.lucene.index.IndexableField; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.ScoreDoc; + +/** + * Holder for a search result page. + */ +public final class SearchResults { + + private long totalHits = 0; + + private int offset = 0; + + private List hits = new ArrayList<>(); + + /** + * Creates a search result page for the given raw Lucene hits. + * + * @param totalHits - total number of hits for this query + * @param docs - array of hits + * @param offset - offset of the current page + * @param searcher - index searcher + * @param fieldsToLoad - fields to load + * @return the search result page + * @throws IOException - if there is a low level IO error. + */ + static SearchResults of(long totalHits, @Nonnull ScoreDoc[] docs, int offset, + @Nonnull IndexSearcher searcher, Set fieldsToLoad) + throws IOException { + SearchResults res = new SearchResults(); + + res.totalHits = totalHits; + + for (ScoreDoc sd : docs) { + Document luceneDoc = (fieldsToLoad == null) ? + searcher.doc(sd.doc) : searcher.doc(sd.doc, fieldsToLoad); + res.hits.add(Doc.of(sd.doc, sd.score, luceneDoc)); + res.offset = offset; + } + + return res; + } + + /** + * Returns the total number of hits for this query. + */ + public long getTotalHits() { + return totalHits; + } + + /** + * Returns the offset of the current page. + */ + public int getOffset() { + return offset; + } + + /** + * Returns the documents of the current page. + */ + public List getHits() { + return ImmutableList.copyOf(hits); + } + + /** + * Returns the size of the current page. + */ + public int size() { + return hits.size(); + } + + private SearchResults() { + } + + /** + * Holder for a hit. + */ + public static class Doc { + private int docId; + private float score; + private Map fieldValues = new HashMap<>(); + + /** + * Creates a hit. + * + * @param docId - document id + * @param score - score of this document for the query + * @param luceneDoc - raw Lucene document + * @return the hit + */ + static Doc of(int docId, float score, @Nonnull Document luceneDoc) { + Doc doc = new Doc(); + doc.docId = docId; + doc.score = score; + Set fields = luceneDoc.getFields().stream().map(IndexableField::name).collect(Collectors.toSet()); + for (String f : fields) { + doc.fieldValues.put(f, luceneDoc.getValues(f)); + } + return doc; + } + + /** + * Returns the document id. + */ + public int getDocId() { + return docId; + } + + /** + * Returns the score of this document for the current query. + */ + public float getScore() { + return score; + } + + /** + * Returns the field data of this document. + */ + public Map getFieldValues() { + return ImmutableMap.copyOf(fieldValues); + } + + private Doc() { + } + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/search/SimilarityConfig.java b/lucene/luke/src/java/org/apache/lucene/luke/models/search/SimilarityConfig.java new file mode 100644 index 000000000000..d7813f415e19 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/search/SimilarityConfig.java @@ -0,0 +1,100 @@ +/* + * 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.models.search; + +/** + * Configurations for Similarity. + */ +public final class SimilarityConfig { + + private final boolean useClassicSimilarity; + + /* BM25Similarity parameters */ + + private final float k1; + + private final float b; + + /* Common parameters */ + + private final boolean discountOverlaps; + + /** {@link SimilarityConfig} builder */ + public static class Builder { + private boolean useClassicSimilarity = false; + private float k1 = 1.2f; + private float b = 0.75f; + private boolean discountOverlaps = true; + + public Builder useClassicSimilarity(boolean val) { + useClassicSimilarity = val; + return this; + } + + public Builder k1(float val) { + k1 = val; + return this; + } + + public Builder b(float val) { + b = val; + return this; + } + + public Builder discountOverlaps(boolean val) { + discountOverlaps = val; + return this; + } + + public SimilarityConfig build() { + return new SimilarityConfig(this); + } + } + + private SimilarityConfig(Builder builder) { + this.useClassicSimilarity = builder.useClassicSimilarity; + this.k1 = builder.k1; + this.b = builder.b; + this.discountOverlaps = builder.discountOverlaps; + } + + public boolean isUseClassicSimilarity() { + return useClassicSimilarity; + } + + public float getK1() { + return k1; + } + + public float getB() { + return b; + } + + public boolean isDiscountOverlaps() { + return discountOverlaps; + } + + public String toString() { + return "SimilarityConfig: [" + + " use classic similarity=" + useClassicSimilarity + ";" + + " discount overlaps=" + discountOverlaps + ";" + + " k1=" + k1 + ";" + + " b=" + b + ";" + + "]"; + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/search/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/models/search/package-info.java new file mode 100644 index 000000000000..63433a1bf2cf --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/search/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. + */ + +/** Models and APIs for the Search tab */ +package org.apache.lucene.luke.models.search; \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexTools.java b/lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexTools.java new file mode 100644 index 000000000000..addb3c86a5a3 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexTools.java @@ -0,0 +1,91 @@ +/* + * 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.models.tools; + +import java.io.PrintStream; +import java.util.Collection; + +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.index.CheckIndex; +import org.apache.lucene.luke.models.LukeException; +import org.apache.lucene.search.Query; + +/** + * A dedicated interface for Luke's various index manipulations. + */ +public interface IndexTools { + + /** + * Execute force merges. + * + *

+ * Merges are executed until there are maxNumSegments segments.
+ * When expunge is true, maxNumSegments parameter is ignored. + *

+ * + * @param expunge - if true, only segments having deleted documents are merged + * @param maxNumSegments - max number of segments + * @param ps - information stream + * @throws LukeException - if an internal error occurs when accessing index + */ + void optimize(boolean expunge, int maxNumSegments, PrintStream ps); + + /** + * Check the current index status. + * + * @param ps information stream + * @return index status + * @throws LukeException - if an internal error occurs when accessing index + */ + CheckIndex.Status checkIndex(PrintStream ps); + + /** + * Try to repair the corrupted index using previously returned index status. + * + *

This method must be called with the return value from {@link IndexTools#checkIndex(PrintStream)}.

+ * + * @param st - index status + * @param ps - information stream + * @throws LukeException - if an internal error occurs when accessing index + */ + void repairIndex(CheckIndex.Status st, PrintStream ps); + + /** + * Add new document to this index. + * + * @param doc - document to be added + * @param analyzer - analyzer for parsing to document + * @throws LukeException - if an internal error occurs when accessing index + */ + void addDocument(Document doc, Analyzer analyzer); + + /** + * Delete documents from this index by the specified query. + * + * @param query - query for deleting + * @throws LukeException - if an internal error occurs when accessing index + */ + void deleteDocuments(Query query); + + /** + * Returns preset {@link Field} classes. + */ + Collection> getPresetFields(); +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexToolsFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexToolsFactory.java new file mode 100644 index 000000000000..c3bd86376a14 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexToolsFactory.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.models.tools; + +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.store.Directory; + +/** Factory of {@link IndexTools} */ +public class IndexToolsFactory { + + public IndexTools newInstance(Directory dir) { + return new IndexToolsImpl(dir, false, false); + } + + public IndexTools newInstance(IndexReader reader, boolean useCompound, boolean keepAllCommits) { + return new IndexToolsImpl(reader, useCompound, keepAllCommits); + } + +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexToolsImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexToolsImpl.java new file mode 100644 index 000000000000..d225201c8a9f --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexToolsImpl.java @@ -0,0 +1,171 @@ +/* + * 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.models.tools; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Arrays; +import java.util.Collection; + +import org.apache.lucene.analysis.Analyzer; +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.CheckIndex; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.luke.models.LukeException; +import org.apache.lucene.luke.models.LukeModel; +import org.apache.lucene.luke.models.util.IndexUtils; +import org.apache.lucene.search.Query; +import org.apache.lucene.store.Directory; + +/** Default implementation of {@link IndexTools} */ +public final class IndexToolsImpl extends LukeModel implements IndexTools { + + private final boolean useCompound; + + private final boolean keepAllCommits; + + @SuppressWarnings("unchecked") + private static final Class[] 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 + }; + + /** + * Constructs an IndexToolsImpl that holds given {@link Directory}. + * + * @param dir - the index directory + * @param useCompound - if true, compound file format is used + * @param keepAllCommits - if true, all commit points are reserved + */ + public IndexToolsImpl(@Nonnull Directory dir, boolean useCompound, boolean keepAllCommits) { + super(dir); + this.useCompound = useCompound; + this.keepAllCommits = keepAllCommits; + } + + /** + * Constructs an IndexToolsImpl that holds given {@link IndexReader}. + * + * @param reader - the index reader + * @param useCompound - if true, compound file format is used + * @param keepAllCommits - if true, all commit points are reserved + */ + public IndexToolsImpl(@Nonnull IndexReader reader, boolean useCompound, boolean keepAllCommits) { + super(reader); + this.useCompound = useCompound; + this.keepAllCommits = keepAllCommits; + } + + @Override + public void optimize(boolean expunge, int maxNumSegments, PrintStream ps) { + if (reader instanceof DirectoryReader) { + Directory dir = ((DirectoryReader) reader).directory(); + try (IndexWriter writer = IndexUtils.createWriter(dir, null, useCompound, keepAllCommits, ps)) { + IndexUtils.optimizeIndex(writer, expunge, maxNumSegments); + } catch (IOException e) { + throw new LukeException("Failed to optimize index", e); + } + } else { + throw new LukeException("Current reader is not a DirectoryReader."); + } + } + + @Override + public CheckIndex.Status checkIndex(PrintStream ps) { + try { + if (dir != null) { + return IndexUtils.checkIndex(dir, ps); + } else if (reader instanceof DirectoryReader) { + Directory dir = ((DirectoryReader) reader).directory(); + return IndexUtils.checkIndex(dir, ps); + } else { + throw new IllegalStateException("Directory is not set."); + } + } catch (Exception e) { + throw new LukeException("Failed to check index.", e); + } + } + + @Override + public void repairIndex(CheckIndex.Status st, PrintStream ps) { + try { + if (dir != null) { + IndexUtils.tryRepairIndex(dir, st, ps); + } else { + throw new IllegalStateException("Directory is not set."); + } + } catch (Exception e) { + throw new LukeException("Failed to repair index.", e); + } + } + + @Override + public void addDocument(Document doc, @Nullable Analyzer analyzer) { + if (reader instanceof DirectoryReader) { + Directory dir = ((DirectoryReader) reader).directory(); + try (IndexWriter writer = IndexUtils.createWriter(dir, analyzer, useCompound, keepAllCommits)) { + writer.addDocument(doc); + writer.commit(); + } catch (IOException e) { + throw new LukeException("Failed to add document", e); + } + } else { + throw new LukeException("Current reader is not an instance of DirectoryReader."); + } + } + + @Override + public void deleteDocuments(@Nonnull Query query) { + if (reader instanceof DirectoryReader) { + Directory dir = ((DirectoryReader) reader).directory(); + try (IndexWriter writer = IndexUtils.createWriter(dir, null, useCompound, keepAllCommits)) { + writer.deleteDocuments(query); + writer.commit(); + } catch (IOException e) { + throw new LukeException("Failed to add document", e); + } + } else { + throw new LukeException("Current reader is not an instance of DirectoryReader."); + } + } + + @Override + @SuppressWarnings("unchecked") + public Collection> getPresetFields() { + return Arrays.asList(presetFieldClasses); + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/tools/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/models/tools/package-info.java new file mode 100644 index 000000000000..cb76b17725e4 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/tools/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. + */ + +/** Models and APIs for various index manipulation */ +package org.apache.lucene.luke.models.tools; \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/util/IndexUtils.java b/lucene/luke/src/java/org/apache/lucene/luke/models/util/IndexUtils.java new file mode 100644 index 000000000000..bb5adec38904 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/util/IndexUtils.java @@ -0,0 +1,502 @@ +/* + * 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.models.util; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; +import java.io.PrintStream; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Constructor; +import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.core.WhitespaceAnalyzer; +import org.apache.lucene.codecs.CodecUtil; +import org.apache.lucene.index.*; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.FSDirectory; +import org.apache.lucene.store.IOContext; +import org.apache.lucene.store.IndexInput; +import org.apache.lucene.store.LockFactory; +import org.apache.lucene.util.Bits; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utilities for various raw index operations. + * + *

+ * This is for internal uses, DO NOT call from UI components or applications. + *

+ */ +public final class IndexUtils { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + /** + * Opens index(es) reader for given index path. + * + * @param indexPath - path to the index directory + * @param dirImpl - class name for the specific directory implementation + * @return index reader + * @throws IOException - if there is a low level IO error. + */ + public static IndexReader openIndex(@Nonnull String indexPath, @Nullable String dirImpl) + throws IOException { + final Path root = FileSystems.getDefault().getPath(indexPath); + final List readers = new ArrayList<>(); + + // find all valid index directories in this directory + Files.walkFileTree(root, new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attrs) throws IOException { + Directory dir = openDirectory(path, dirImpl); + try { + DirectoryReader dr = DirectoryReader.open(dir); + readers.add(dr); + } catch (IOException e) { + log.warn(e.getMessage(), e); + } + return FileVisitResult.CONTINUE; + } + }); + + if (readers.isEmpty()) { + throw new RuntimeException("No valid directory at the location: " + indexPath); + } + + log.info(String.format(Locale.ENGLISH, "IndexReaders (%d leaf readers) successfully opened. Index path=%s", readers.size(), indexPath)); + + if (readers.size() == 1) { + return readers.get(0); + } else { + return new MultiReader(readers.toArray(new IndexReader[0])); + } + } + + /** + * Opens an index directory for given index path. + * + *

This can be used to open/repair corrupted indexes.

+ * + * @param dirPath - index directory path + * @param dirImpl - class name for the specific directory implementation + * @return directory + * @throws IOException - if there is a low level IO error. + */ + public static Directory openDirectory(@Nonnull String dirPath, @Nullable String dirImpl) throws IOException { + final Path path = FileSystems.getDefault().getPath(dirPath); + Directory dir = openDirectory(path, dirImpl); + log.info(String.format(Locale.ENGLISH, "DirectoryReader successfully opened. Directory path=%s", dirPath)); + return dir; + } + + private static Directory openDirectory(@Nonnull Path path, String dirImpl) throws IOException { + if (!Files.exists(path)) { + throw new IllegalArgumentException("Index directory doesn't exist."); + } + + Directory dir; + if (dirImpl == null || dirImpl.equalsIgnoreCase("org.apache.lucene.store.FSDirectory")) { + dir = FSDirectory.open(path); + } else { + try { + Class implClazz = Class.forName(dirImpl); + Constructor constr = implClazz.getConstructor(Path.class); + if (constr != null) { + dir = (Directory) constr.newInstance(path); + } else { + constr = implClazz.getConstructor(Path.class, LockFactory.class); + dir = (Directory) constr.newInstance(path, null); + } + } catch (Exception e) { + log.warn(e.getMessage(), e); + throw new IllegalArgumentException("Invalid directory implementation class: " + dirImpl); + } + } + return dir; + } + + /** + * Close index directory. + * + * @param dir - index directory to be closed + */ + public static void close(Directory dir) { + try { + if (dir != null) { + dir.close(); + log.info("Directory successfully closed."); + } + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } + + /** + * Close index reader. + * + * @param reader - index reader to be closed + */ + public static void close(IndexReader reader) { + try { + if (reader != null) { + reader.close(); + log.info("IndexReader successfully closed."); + if (reader instanceof DirectoryReader) { + Directory dir = ((DirectoryReader) reader).directory(); + dir.close(); + log.info("Directory successfully closed."); + } + } + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } + + /** + * Create an index writer. + * + * @param dir - index directory + * @param analyzer - analyzer used by the index writer + * @param useCompound - if true, compound index files are used + * @param keepAllCommits - if true, all commit generations are kept + * @return new index writer + * @throws IOException - if there is a low level IO error. + */ + public static IndexWriter createWriter(@Nonnull Directory dir, Analyzer analyzer, boolean useCompound, boolean keepAllCommits) throws IOException { + return createWriter(dir, analyzer, useCompound, keepAllCommits, null); + } + + /** + * Create an index writer. + * + * @param dir - index directory + * @param analyzer - analyser used by the index writer + * @param useCompound - if true, compound index files are used + * @param keepAllCommits - if true, all commit generations are kept + * @param ps - information stream + * @return new index writer + * @throws IOException - if there is a low level IO error. + */ + public static IndexWriter createWriter(@Nonnull Directory dir, Analyzer analyzer, boolean useCompound, boolean keepAllCommits, + @Nullable PrintStream ps) throws IOException { + + IndexWriterConfig config = new IndexWriterConfig(analyzer == null ? new WhitespaceAnalyzer() : analyzer); + config.setUseCompoundFile(useCompound); + if (ps != null) { + config.setInfoStream(ps); + } + if (keepAllCommits) { + config.setIndexDeletionPolicy(NoDeletionPolicy.INSTANCE); + } else { + config.setIndexDeletionPolicy(new KeepOnlyLastCommitDeletionPolicy()); + } + + return new IndexWriter(dir, config); + } + + /** + * Execute force merge with the index writer. + * + * @param writer - index writer + * @param expunge - if true, only segments having deleted documents are merged + * @param maxNumSegments - max number of segments + * @throws IOException - if there is a low level IO error. + */ + public static void optimizeIndex(@Nonnull IndexWriter writer, boolean expunge, int maxNumSegments) throws IOException { + if (expunge) { + writer.forceMergeDeletes(true); + } else { + writer.forceMerge(maxNumSegments, true); + } + } + + /** + * Check the index status. + * + * @param dir - index directory for checking + * @param ps - information stream + * @return - index status + * @throws IOException - if there is a low level IO error. + */ + public static CheckIndex.Status checkIndex(@Nonnull Directory dir, @Nullable PrintStream ps) throws IOException { + try (CheckIndex ci = new CheckIndex(dir)) { + if (ps != null) { + ci.setInfoStream(ps); + } + return ci.checkIndex(); + } + } + + /** + * Try to repair the corrupted index using previously returned index status. + * + * @param dir - index directory for repairing + * @param st - index status + * @param ps - information stream + * @throws IOException - if there is a low level IO error. + */ + public static void tryRepairIndex(@Nonnull Directory dir, @Nonnull CheckIndex.Status st, @Nullable PrintStream ps) throws IOException { + try (CheckIndex ci = new CheckIndex(dir)) { + if (ps != null) { + ci.setInfoStream(ps); + } + ci.exorciseIndex(st); + } + } + + /** + * Returns the string representation for Lucene codec version when the index was written. + * + * @param dir - index directory + * @throws IOException - if there is a low level IO error. + */ + public static String getIndexFormat(@Nonnull Directory dir) throws IOException { + return new SegmentInfos.FindSegmentsFile(dir) { + @Override + protected String doBody(String segmentFileName) throws IOException { + String format = "unknown"; + try (IndexInput in = dir.openInput(segmentFileName, IOContext.READ)) { + if (CodecUtil.CODEC_MAGIC == in.readInt()) { + int actualVersion = CodecUtil.checkHeaderNoMagic(in, "segments", SegmentInfos.VERSION_70, Integer.MAX_VALUE); + if (actualVersion == SegmentInfos.VERSION_70) { + format = "Lucene 7.0 or later"; + } else if (actualVersion == SegmentInfos.VERSION_72) { + format = "Lucene 7.2 or later"; + } else if (actualVersion == SegmentInfos.VERSION_74) { + format = "Lucene 7.4 or later"; + } else if (actualVersion > SegmentInfos.VERSION_74) { + format = "Lucene 7.4 or later (UNSUPPORTED)"; + } + } else { + format = "Lucene 6.x or prior (UNSUPPORTED)"; + } + } + return format; + } + }.run(); + } + + /** + * Returns user data written with the specified commit. + * + * @param ic - index commit + * @throws IOException - if there is a low level IO error. + */ + public static String getCommitUserData(@Nonnull IndexCommit ic) throws IOException { + Map userDataMap = ic.getUserData(); + if (userDataMap != null) { + return userDataMap.toString(); + } else { + return "--"; + } + } + + /** + * Collect all terms and their counts in the specified fields. + * + * @param reader - index reader + * @param fields - field names + * @return a map contains terms and their occurrence frequencies + * @throws IOException - if there is a low level IO error. + */ + public static Map countTerms(IndexReader reader, Collection fields) throws IOException { + Map res = new HashMap<>(); + for (String field : fields) { + if (!res.containsKey(field)) { + res.put(field, 0L); + } + Terms terms = MultiTerms.getTerms(reader, field); + if (terms != null) { + TermsEnum te = terms.iterator(); + while (te.next() != null) { + res.put(field, res.get(field) + 1); + } + } + } + return res; + } + + /** + * Returns true if the document with the specified docid is not deleted, otherwise false. + * + * @param reader - index reader + * @param docid - document id + */ + public static boolean isLive(IndexReader reader, int docid) { + if (reader instanceof LeafReader) { + Bits liveDocs = ((LeafReader) reader).getLiveDocs(); + return liveDocs == null || liveDocs.get(docid); + } else { + boolean allLive = true; + boolean live = false; + for (LeafReaderContext ctx : reader.leaves()) { + LeafReader lr = ctx.reader(); + Bits liveDocs = lr.getLiveDocs(); + if (liveDocs != null) { + allLive = false; + live = live || liveDocs.get(docid); + } + } + // return true if there are no deleted documents or the docid is included in any segments. + return allLive || live; + } + } + + /** + * Returns field {@link FieldInfos} in the index. + * + * @param reader - index reader + */ + public static FieldInfos getFieldInfos(IndexReader reader) { + if (reader instanceof LeafReader) { + return ((LeafReader) reader).getFieldInfos(); + } else { + return FieldInfos.getMergedFieldInfos(reader); + } + } + + /** + * Returns the {@link FieldInfo} referenced by the field. + * + * @param reader - index reader + * @param fieldName - field name + */ + public static FieldInfo getFieldInfo(IndexReader reader, String fieldName) { + return getFieldInfos(reader).fieldInfo(fieldName); + } + + /** + * Returns all field names in the index. + * + * @param reader - index reader + */ + public static Collection getFieldNames(IndexReader reader) { + return StreamSupport.stream(getFieldInfos(reader).spliterator(), false) + .map(f -> f.name) + .collect(Collectors.toList()); + } + + /** + * Returns the {@link Terms} for the specified field. + * + * @param reader - index reader + * @param field - field name + * @throws IOException - if there is a low level IO error. + */ + public static Terms getTerms(IndexReader reader, String field) throws IOException { + if (reader instanceof LeafReader) { + return ((LeafReader) reader).terms(field); + } else { + return MultiTerms.getTerms(reader, field); + } + } + + /** + * Returns the {@link BinaryDocValues} for the specified field. + * + * @param reader - index reader + * @param field - field name + * @throws IOException - if there is a low level IO error. + */ + public static BinaryDocValues getBinaryDocValues(IndexReader reader, String field) throws IOException { + if (reader instanceof LeafReader) { + return ((LeafReader) reader).getBinaryDocValues(field); + } else { + return MultiDocValues.getBinaryValues(reader, field); + } + } + + /** + * Returns the {@link NumericDocValues} for the specified field. + * + * @param reader - index reader + * @param field - field name + * @throws IOException - if there is a low level IO error. + */ + public static NumericDocValues getNumericDocValues(IndexReader reader, String field) throws IOException { + if (reader instanceof LeafReader) { + return ((LeafReader) reader).getNumericDocValues(field); + } else { + return MultiDocValues.getNumericValues(reader, field); + } + } + + /** + * Returns the {@link SortedNumericDocValues} for the specified field. + * + * @param reader - index reader + * @param field - field name + * @throws IOException - if there is a low level IO error. + */ + public static SortedNumericDocValues getSortedNumericDocValues(IndexReader reader, String field) throws IOException { + if (reader instanceof LeafReader) { + return ((LeafReader) reader).getSortedNumericDocValues(field); + } else { + return MultiDocValues.getSortedNumericValues(reader, field); + } + } + + /** + * Returns the {@link SortedDocValues} for the specified field. + * + * @param reader - index reader + * @param field - field name + * @throws IOException - if there is a low level IO error. + */ + public static SortedDocValues getSortedDocValues(IndexReader reader, String field) throws IOException { + if (reader instanceof LeafReader) { + return ((LeafReader) reader).getSortedDocValues(field); + } else { + return MultiDocValues.getSortedValues(reader, field); + } + } + + /** + * Returns the {@link SortedSetDocValues} for the specified field. + * + * @param reader - index reader + * @param field - field name + * @throws IOException - if there is a low level IO error. + */ + public static SortedSetDocValues getSortedSetDocvalues(IndexReader reader, String field) throws IOException { + if (reader instanceof LeafReader) { + return ((LeafReader) reader).getSortedSetDocValues(field); + } else { + return MultiDocValues.getSortedSetValues(reader, field); + } + } + + private IndexUtils() { + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/util/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/models/util/package-info.java new file mode 100644 index 000000000000..29354bd9273b --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/util/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. + */ + +/** Utilities for models and APIs */ +package org.apache.lucene.luke.models.util; \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/package-info.java new file mode 100644 index 000000000000..9c6a51e1c8b7 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/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. + */ + +/** Luke : Lucene toolbox project */ +package org.apache.lucene.luke; \ No newline at end of file diff --git a/lucene/luke/src/java/org/apache/lucene/luke/util/BytesRefUtils.java b/lucene/luke/src/java/org/apache/lucene/luke/util/BytesRefUtils.java new file mode 100644 index 000000000000..4c7cf18657f7 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/util/BytesRefUtils.java @@ -0,0 +1,37 @@ +/* + * 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.util; + +import org.apache.lucene.util.BytesRef; + +/** + * An utility class for handling {@link BytesRef} objects. + */ +public final class BytesRefUtils { + + public static String decode(BytesRef ref) { + try { + return ref.utf8ToString(); + } catch (Exception e) { + return ref.toString(); + } + } + + private BytesRefUtils() { + } +} diff --git a/lucene/luke/src/java/org/apache/lucene/luke/util/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/util/package-info.java new file mode 100644 index 000000000000..e9830cf28e68 --- /dev/null +++ b/lucene/luke/src/java/org/apache/lucene/luke/util/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. + */ + +/** General utilities */ +package org.apache.lucene.luke.util; \ No newline at end of file diff --git a/lucene/luke/src/java/overview.html b/lucene/luke/src/java/overview.html new file mode 100644 index 000000000000..534560c124e6 --- /dev/null +++ b/lucene/luke/src/java/overview.html @@ -0,0 +1,26 @@ + + + + + + Luke + + +Luke - Lucene Toolbox + + \ No newline at end of file diff --git a/lucene/luke/src/resources/font/ElegantIcons.ttf b/lucene/luke/src/resources/font/ElegantIcons.ttf new file mode 100644 index 0000000000000000000000000000000000000000..12ff680025e13dc4f5ab6d7e659e4e2fbdad7273 GIT binary patch literal 59388 zcmdqK2bf&dkv4qK?Hf9$+hIa?PluWA$*I!>%}h%g<%p1kkOXQ835jHc!5D-MO)v%w zwibf}4vcfcHqHsdEXH_kY%jL4y=$}3;DFa3UbE}9ch~D}eQ%w6Z->zc^L_vGKmYUm zElu}LeNLS^RduTBR8U5{sf+nzv|#+M=o33vP%$twt#Do9>4s`t1kKQ zH?KnThXuj0L!hzYZ`&uvHy`k<_@m$uaX}Eif92=;&CgS{Q_qMZdk*JTLBGwv!=5|! zj4&(;r)o7p6nRVZ74s$26?UocfY8M+(Ki`?s$@LE@THT2ELhoQb|J2hR%-8k{*Bll~LgFQXRZmrgVZfHGaG2-`6GY+T`tvydApVv3zd2zZpN|P| zAroWIj&9Pr#CEZ_+`rhoHzs)8#AjB`fqVP>gC zx}*0d;0gLG^QTI}7F^el8^ej?33^HRd%eKh!Bc{81OD{BaZrDiaAXmBcn|f9yf-@Y z;cpH-RPZO@>dkmx{He)<-e$9L<UltX8I=xG5pQ*qcYwrI4j|=@!De?#__rITP1#v@I`+Sfzi2m3}Usg%iSc!s|e@*Mn9o!i~aBptoDVLAQdt zZWC@7?hxK2+$r28yjggQa8mdi;coENJ;J@h+l03Z?-1T8+$X#XT=yQ~y~6$Ak@pGj z7d`+!{gCis;UmIFg^z)MKLP3SN#RqFXAcUW5k4z?PWZg=1>uXrmxM11UlG14d`);r z_`2}0@CZ2bZ-qyNZ-BGEDLgKGOZYqC+roE*Cxq_`-xIzs{Jrp`@B`t8!jFWfgdYn( z5q>KCgYb{S&xD_Yf-JZ=U-OG@O0uF_ZFYy#<@R`eek~9Th1=RYIwR3oS3HqSb@!w*y?y-y zgG0k3qhsUQT)t48n4FqkF*7??TDfX9R?&6qH*DOrdCS&q+t1o@_Rd|q&)IYC-hKPe zJO4oWU~_N(Umv?6We$O=KQB}Q#r;&FaPK9qPmCzgzpQW$SI$1?%5g|HYQJ?X=xv`=LE< z-)sM{{fG8HIffnQI~E)-Io-|)=f%!joQuw1yJD`ZT#vb9?hkt6o_Bj{-fO(?@cz(e z^_6^A`M&R0{5$-2_#fBWv>n==+Vg?+fe!}b!K;J647G(W41F~`6}~fEYkPIuXWQ-V zSGPaY(cRhK`RdMv&hJL-k=r7VMuliDdQJ4HSRrAtP|$)2{JtJD7UY`T(uK9kNI&b%)3=3cS4-20uryZavR z`*Z(z|Ka}o`oBLQ4onQZcTgPc9o#y2)!==DKN<24Z5+C7=(9sl4=cl?!#js>8-8d+ z7#SUT^~gI$elt2adh6()#|FoCj9owW*x0{~r^k1WSH>5||2;dE-I={L`wzKD?xx(Y z^WOaC{5|=n@_#N27Y-Gj#fys%OtejuCN7?M+r$qidnb2K-Z%NDsrb}|Q=gjp^|XI_ zc>4P3N2hBmHm|6x`0|RUXWC{iolVYOKl|XEI2WIro!dY6{<%Mu4wvpJ{lm(QEALqO zmsO=z*RT5cs^6^cSbbvkOY@QW9rLf6zhnOUYbMqlT65=`pRFBV`|-8EURPN6(RKf} z{@D7@um6_~$qg58`0z&W##=W&yh+%U-n4zwyEnTxuiboN^HW<2TfV%dwsp_eA8za2 z_R(#%?ep7j+Wyp8k+V+j@b1{LY_6u!-Pz;1aXb*5E#tXT0Dp90H-7HvQ1|;~B ztz??{y8Uq!@`Y`{)gkG^&6dfZv zjNO27LFgnHL*s=R#xUkwhGhxP0hUzMF#;1%ur&^cEY}{omH=HqBNp)`I=r^_kkcu- zCAJ0+;Ub4~PSD$hMpq0KSx|2~2X5qz213(}DFL*|zr`$oYQbns?r}QVywMA8&}7!W zCb>CIp`ifY_mQU9om8TXrIV@>W~rn?O^z1}MGPUwzNCLeTGCP#(6V`1b~tKZqY>%d z>UfB=huFIb4+UyB5DJfuVgAu3#{-B!W%=AWe1w=A9au(FU;7%G<@jI(D$o>m5|%i; zkDYoBtJ$+c0-Y=+)6-~-f3>93YzZy*^q{HcbM^*YE$Q@I!oBX=tGbt5VRxfnQF!C2 zXW6yjpP7n_K&}Y~=XI}4=_Yb>ON3hhLj-n} z&~0*w$$w)F#;miFD2SF8?22Ggeu3Ssqg(uuA~sDBh!@=2a>1l2;w{*%CT}gFQ_KcP zco|mfTSERnJ)P-(+8|(JLe__Ko%@>m9y}90FUygq6F9Zn%$+}NrS)atB%F)DwZxug z4}qU@IESpu=lEcO?r<|-6$Qwzd(@&+BYI1p52i;&BJjTR0Pb0j`E1(?8K88}@GaI(qg*t1RD zg8>Q7T3t36V9|B5QJwn$%n;=8x&Z1nZ$esvk3G*(7c}0Ddn)egim8%;pm&wVUTn z-yr$a(j=_}B?}fF&M;=DFbf%ZG}tXHE;wOIGkwwKg6DJRo#zNSj^T^P9HIB@-w(tc zljUQMF#k>k-;wA$Ow|P@mu2j-<}Wcz^S7w4MPWazhIs+2Mv+$W+z8`%Qv%;SM#D6o z=O-@7Y>ViF!2;V43ud1BEPy)cyR@>ZKQQ5P?%#i`4zT0A^FZ%!5Uym`urj=IQ9*!A zOBo^+!&LPX%oiVZhQ0O@7$@>2kh0|i&hTsb3DzWj=KP=YW%+z(*tu($Gi;Tl0}!qb z$F5y?7_f=3V$1BcYzz9qO4VavB+F4k8ikG_I%fx{ai`f;Nbk%z{~5LJ)O*?=(wjek z&S?CiP!TR-m%&@;gx8C(2d%}x9d$vJ#xK3W1QQMYXUIw4hveV2>&NEE(3$Ji1cJ*L zEZTmnaEw*hKFkaJ4Z7^7wTPpcRGC7aunlSlzS(tp4}_a~7Y085m);4^ob_%9sX88M z92OzT`RQSdBLE)+IK43^8Id?1OS_m%=L+MY0PEDr53>nc^gMp&osZx7&Ihi&_JL~| zRy@!m<{Wg%9Jqex#(Y0OD5sCsEH+YB=0R6hFnU3Wcx*L@G1IPt&$-L%+~E6!^T%IU)K@2?I1MO{|hJc*Wq9( z%s2)u8*Jp%nt))N8cgI2n1qQZ*?bE`4R}1@+a6&^=P6EpA)Tu-%{+GclwMvY4ka3F zV(I#s+62>gcJ0y$nb`X%=V+Z}u5JMdI5Ov(_%A{Fn=aFJ#>=$uAlB5DY578fubtO7 z@uaab;bc5he~u zyPKf;d9&0|>zr&L_di-8W90jWy;c(7wJv6%_H&^z8YH<9I{B;$nRy&vJr4UqT2P#> z*U2)w&gEn>$@ix3_KfC>g-o6_0RGi+=D>Gk)okHI`nPxszz2g@@G$&P`>9{33xV)x z7OP8^Y>rHtCF8c1Vle2sNe-xjM~LIflDW@7Y!U{-LERF{uq&JJ`?%zk4%zLJ@(o3{+1O$; zLf>a%2jhGt{nW8lI)>KrubxV+Rlu&^mQR89|Qar?3Ej6r9o3)44(8dbWGj$=PPJ- zJj69i=)nWVa=NCBWo7=KhqBNfXIK66Jl2=8^H*hjteGDqb#}$hHF%=zKzkK|EQzWB zQYacG6ixq0CeVYY=zS5*;J@B~MJT}!SSBr@m^D;d!_F`?ac+v7JdST1rzXsU#g;RE zS*QCl*6kAPIIa^llmf1CsP~2)=bj5#_v``m#?hs%r~C%T%>iL0_c?T(wu?_%m}*5LvE z65;R`*|^njm(<#?4k&I-dG!){20l*Xn*?Dp%dy%r<=Z)0RgU6T^19GKbz^NNp2TW) zTHC2+Y)_k(4xfqEzD)erCt^vD%T3FYv89&jUthhrF<{PFOTVw(vkgkywsWy)8%NG? zYtP^0bGm1*IlFX(j?g!QekHQ1Kw~E>Z;3p@9FjuH=Wuk{c`g>%B~QKWZBM=J17Er2 zmap7=%;Q%s&8nJ{DeEzxOXmDKnN{)V+n%a@@#e4IeDhZywoBFzjnW*iGNzx;zW9)p zU^D%PKCGcV9JXK}41rCa5UlJC#V=!;>6;?xWim zi9P6zRZr~RFnJq@PJQF>i3e(b#@(ZLpIfJe=05W;-{;Fv#j-GLMYiyKeJ3;+TSQz9;H6bgVugqZt-W#xgj5iUnoFlOZc+Irums+ zYLTnmnY5e1oC}1;!lUDuJ2*VYz#YY0A*&VAgya_krv!7l;sr%|xnbVx_n_xj)K2E!qEj3p5!`*-zef{!+o9nibkl6YY@fs7nsTG{&|s96!Er z{Q6tYKmV5V*#ZuaFT8}q^KZek4SJ#Pk|;9dj`{6wt`Vn`thtp_!o>E8iR~?oT})SK ziym#7yi09@Cd@WX^N^$j(Va;r>|$MRks!Kxp;9Nk776wBCaMEjLwBseuTY{9_cX|j zn$7AQqPNrZEvW^rPV%9W(MnpsA4V&UnEUJv-&*rA7nN`O00;1`9h%mm>F;rVgKgw8 zvKmeXN- z8GU9cDg_0yxeQ&cV$9tD;F~`43~Egn6%8(=C0D0d{qvQe?;?Flo8ArdRrE`z1pSk? zq0uY!TK2=64t{_w%?NzN(m7UNaUgO<3&7r_^vU=wZQZJ_W-Nt8^-2O*ts@3gNlz@#HgIqG^Z*R3(t!rLwRjf?z^xE83tLk;VU$)9FyU)U8m(`(IWry49 zmFq zhqOwa6OV6ioT-gx{v#fL={SGvBz@x~KRk}Zx5wkGLTrRtHa_CPG{6M3L-F`uzrf0bnI>Eq_=?BNPtuLk@P$SRGwNyh zipOcblmhdLHG{_otfrhuBPM&Fm zeCY8wjghd#4+v2p-cqf?+t-I`47{YD86SMT^`5G+=h>5j49~tGXAqxdV?{Ea0%7)i zvi7gOEc4VJn@_rXBX03R=J91~|H@Y2JkpCTj)tBXI;8bF=-e5*cp`t4dAY9(Hlx^6;!LTBnh zhhdStl;~AXg|nEE4Afk+Q+77``_Ql2oc&Y%PWItn#J^EnAl#JE@m=WbgRFqQ=w>-y zTlhx&7aSnmC4EHK&nobM!G%i>|N|fLMPXKgBcG?W%9*r zxVe8*4`El@TwitBZn5FNaS-M@@Xam2{Iu!5{@|0O;@`Zw4%aDx1K$MKE5d`R2fac( z{7<95PQSksJRk`YWcz=E`5f3i>kyLQI16097KL+IPbae&gh0d*VY<4-{`?H0_@1?C ze)e_0R*OCROifqU`MZZL_m!%0w|`x8J|04-#|Rz(0+%oyRmA7ME!q zLF7UzBLpd_fkpTUEJ#wC5ISU>a&4+*@U_1LXo_9qTQSd>_EL8!jRT);?WR_eDBvl# zE5PQ~tRU#Rl7(czIn+tVvR_WGpPYewc)bu~*F(>-3BB+t*5NU9xHAFuG>2f_+D-W- zKy16n#SI@Cza zp^`@lGFOP8<3}Kux@jgDoXQI6rRX}HfnF+yApQspE+XLRQB;Kkg6EncVUI{xos;SB z?di$-oujoj$)3s;gS)P}`opg&IyJ?8!J959uaAzZvZmP-$(m=WfVSl|Zjaj)Y9E+d z^C>G!*p6=6c+o{0*X5!e*00X&-aRwB)6Og|tIzLH2hH_?Arm4BJ!BoN-!U-@&;Xz> zok^N9JZcGqiZcjZKtD;IgaNUSqYnH?XS1Cr=a=uR|#o6^Bn0yvtrP=3q`h*3B!Pg`QxOV^kwTi={s?%GxOsnc&o1nqd#(1}jxh_9>xl6RV5ZZp;HRm}Qwn#+8{~usC zvJ&hbaETvE$aoQ)z~_Q7o z!MFn_v~kBGF$-c9tr|=AM%9T?yHk~>){nPi!vdacDSFyP+RZpkz77bKov}o|B34}o z7R<18AVu6uXMz~XGB;c0?e9$BBc~1PT?D-d;LTh0aY2u2_PT%OWd^fkh+JMJPdy8H z^)R%05B9R_BXcv|)mK+##!Ri9UB&mz<2=CP+T-N!wy9;PEbL*vPyQ8($+&h6w}C3S z%s0HaABEdJq$5yVf$m3%l9kg?5_PjdCb`U2IA1~`X$=XJHa(B4ZaF_r;zYN0;-N>b zg?9>O+0S|3Ssh5gquL?opx(Sx)iWk2nu%B8byBI>t01}U+b7U<7P_UW(()6Oqo?|IjoSI@~N&)ueK@g z_F9!+jcj+je1UX_)!yl~q^JBUHZRZ+J1#w9A$Rer_VzB>Hf3S1*$&ALWp-`5WSg|u zm6b`=+18QYqFh9Ja2s|$z=oPuuE&&G^JbTklzn>-W_Lg$B-kw~Y8 zS&od1d@etWg$pR*qGc3o0Et96J4NchuUPD3rQV5&-r9F8Vl0!1IX!G+KEKIgvD-b^ z;Ogi}SG`^g7du$;+|F4=gg<(L$wO1a62mr_nA^B1?{dK}#CmdRzz2=PfbncVm`v#a zMk$aS@EHa!0jDd(pz~k=bAk9b4y6LMc^FAJd?zrS8Z{$t@MUsrLuFv^gSg_L7y7r8 zov<~M&cs}9aDhb(OpVgW>nolSFasYKJrF+0W=b=;7-hT(l6nf6UYMg}N8c%_6ela&BIZ4YX0 zU#~MUBs=YH8;!8`s6Ot|ZdDcS>gZfwvCvO*S*&i)x3$T(`PCbH@KwOS5{O7|N(_c= z7K_I>vd!idMH*s#=mT>Kv%9QJJ*#LSW}-JUQP-?`!;oH@9>m9arIFZi=vx)4CC7UKtAv`?{Fye}+vTEGy0|#7w*0Q<- zB#zkD_#ZgTM**-9ui!d|cEP3b19AdDa+Yfgb+iEW{*0nNmt3491hvs{U**AU%-yBI%{CsW} zIgz?=ql(=T@>bF+u>$cUTfpuET!@mRa@s=HbQZHC!%?ZxJit*~#9wBN^F7pQNMrgV zJ)k=9dwyT#PSN@4r9($RpJXVY%4n^?AL$ zMLK5j_=c|jS~cF+g(SQ(Y{DwdR~6F~FP*|JXf<9cAyKQ>X6wZV2&^j}kC6z49JOG* z`ya^BKF}0m*9_?b=<7$i9)^IMdR#d1aX6UDr|{R2Mt>j-{+^cL&$c;VvfFnxXf1~Spf~QiU_vgDTip##Js%v`bbl-Y=`=gb=EG+yt?BpK6@^jD(=>)4mF*}9x zg$t2A`dZ|M-i6H11!QyzMbfIwze0v9hz)!~wv+0Z#Pv{E$>fNxaD;y(_wf%)8~;}* z27&>8PeINMKLELjcv4Yw`5b?;FgD9nx{%J`9qS@B7Gt!q>J8BvOGgD2GMhS9has=| zqiJ;8=j)A)v``h9+dGG3F%U>~^@mkkED;EZ^5D(Xd`Fvhf8*}O zxZJ5fvAffE>E+%?cBjj+w!i7Pvd7_eI`&pQn!{$d+if<7=50Eb6d4he7Ry$*)1um$ zb?(ucyUUa&1aj!cW_3c9+F8kg~%scPK zHGY%mQWh$pg$2+8?HUt3U~gW=vcSE`hMHK=ZT6%_mOEBUC^}h1S9r$NtS_ajg`5}4 z4!6VMUPGcFPH(h3Z*!jm&LF=Z5hBUu0eZ;67WqxmR`I`7TLe71O-b^M;6YrH0gEYp zvjsyZ)gvi*gS^>~@FwpjKVnN)ZVnLpNUg$knG)A!c*{L0u5i*AxpMVW>Ac6U5p z@}?XX@q{SZqF%LBEAyeSlIV5E|vnS2-$*B8vFnY@W|4s-3>8#mridsX^`#agLCs|*P-S-PH)7cm}fEr0NDau{}uFK zF86eHUZ)ef01OL;UaxUkpxcS$X98l$$3&<#NIB7yPXuMozaV=+!M&YO#o)={Dw+A& zGW}dJBrdBR=d4F>ltE^57n|NGbZX_?A`$80+)83Se5`y{2UFi{*N^pgc%)AmYdzWb z6T&LP_X=pZPZ*!jX{S+503k+L;&lo^_Ukx5q_}p7JC!qVIDxe;nZlk?nVY0w6*6LL zm)g`sAcA5AHY>Y?kXrkgN-?0_Zm*hE10GoBiqqm6R?lUx&r;N2?P=!X<}d%g#$M~T z?RpavH>xC863phgKM-VjXZbxy0)|7C2|kSB_v~5n6ln-rG!k`(v4Z0;hDcNeS%brj zEIulXK*=w#E%U)zo91); zJwj)n9IcI;@7in~9X8v&=9FN=QO?R(nfQEgGzkWsKhDO(FoMmBXHDZ!$oL`s~~6iW598UvX-NFKB+Yk$CgX0tkK=Q|{(mf!4h**jc!hD6Pl z7Oe-^n*W4+B|I&lk6z4S2^z2w3WDX3JIQ9@B>?l%4ja5Z_2W=M-_zqa__l)nK)yOk zoVlI53`PqZK}=gN+=BNFBRs|~&RtDCt5v#oaQDDf|hu9#mns~@f0k)7yWASt=f z`*)o^J*WVIH_)6?w~zGwr!?XL*5W#eK0s^kL1FVR#<2FmJ0NmD@0q3;5{$4h z+#4Myg=7UA1CJYOK#PC$niaimNwF=h_&X-QH7`Tn#dUUct8qSP}o5*s^l zjwq#a-9CR$u18}3h%bz-*^Mv625KLHCzx3zrET4s{k={*ZR0qcnSC2)Q|za^XRMB% zbGD8JgJWCI>2X+RfNN2>8T9fVz}*eaODHO2{&2f_hKog*Kd7HlfEfg{iS`Y`K?RO! zZowd$itHCyI*Qz0iK&UNZ zOWl*QMcN*7#oul^kLRPwA0K{^s_w5bv3|yI7RuHtS@xC5T4k~{$tpk`?cV{J zTxRz`HVIhY$%A4p@Q9lzV2$iDyuq;c2bi2_ww$v;c5m%Roi4}{R#j~!ckKZr?+mHR ziZ`paicNhBd!$yvC)RfW7=PB6yq-9-68Ah8A8dMgF0=N^uyup zV$45Z2L=VuFZZLUQHl&|7+EmwrY)x2Ps<5yK!T)`L+mqub8;>*!bXzr8wZ0W(3?`L z1|!kH+`ZS)npC?FhDIqkc%wTI4YKNRV)gK+Atpw!By3l0oxRc8Ny0d*0L`_s<-al za~mOomQ2@`_g#tO4l<$_O2@9b_9(a-6H50hVC8d<9^P+qKEENrCgOXC`dUeuu%vP7 z`+9ukm%zbf%xfyibXsiGuo70WJR$;(EJbpf6*GlQF{Bs^eeH=&*vZ&5Jk}Wwwv7*s z=T42gz2o6ktH;BBeqr>#$DNMx05di{e@jnayoqfXx2fa78^=dS#zu1EPY#bO{B&So zeDDyV2y&gTH&yP-ht>u$Q_T2CML~8+7Xu))`&A5v`7VSob8+_*7r*Uo8=ViX+;`s1 zuX^v7qSwarBkyJcwp!UU&v7F0lQ{L)ov$n?lOW#a=Hf?^Pu#Ytl2spr`v>>1e2 zu$i?2mMorvYuFzM0L&FNZiY~Xu!^V$+)0iwCHzizulp08%oUOCk@jfxige)txBI5% zlMej>cE8*GKmpI;v2BqnGVTX(y>@NOX*+*}@K3f7_S4CSK7v(DK+Pf2lVF?yFkVss zRva`bnPA~@Nq~iBCItLp_p)0zU3jU7YZF87P<@V!`|XzSg>9R{QH{N(<8nU%T2D-FYo#Fa3F2C{+lFaZ%v_r96GW$>V zZLGu&o}-n5zLLkfwFYbPxxzuzJiHPWK?398ex>O>&A9+O*YwFH!;&NDm8Nf?3N}0{+>!hi|h7m9@?b{FkdXRkiK4IjvUbD!T`Jmt78@(+U5W=5}itZ_w)v&idMX zzP5E-JD$P97>xFWx&eMtJaLol1+3oV@qmWO5bI_pI6S|aIY-{2<(xsd7fO@#D%nE9*$QjSz{ZqoW zE4pic9KX4nImTzUU16~J?kl#s$L=c{`Vj5QxeRX%m&#F=fCN+#X54B& z_S6{;mSvR;K2dXg0az~i|;`XYcToSeZlDQ#lNHfdyxzqTM zBh--W+f*2fwW*@0w8ut^oBD7GJ7ojgbHf3vH87mpK1f+!qicp;uHiMKaKq5D0UhiV z`wrq~dwE40k=%uDa3tR?ulw^5)}@9sYB4j1TcYF)+%aS8wawfSaJmn-S-e(U~?zp4l_`BAuc~>oM3B2C#e|^A`b|1V$b1Krxm6GDr?l`#nz6~4hyDW6q zT~y4i1rB&gPyy;?;qbj882(E4?4aH^)=%KzFYFtjC0HOH7FLVlOp$Hgy6w|9#NPAn z==H4o{`)(w`}B?-pS}*TGGuF3nSke^mG}7Qc_wUq@P^np`x|Vrukt@hq~& zz{+zhGbd&|Viy|)onSQtslw33A&f8Vl2Zf*7kQ{_rmv0AAz&3_nWVid+C!%bb@Vm2 zJCbp~cmBmS-N`knoykKo^ZLGt-OOS-7$`{=)on+O0#%-~LpeF)tuc2d;(pKk8&hkN z-D}QHUci)Z`n*g|=j?N`s(DbUN-70{=_0nDmcK!|hq0!v1TN~(p=!ea)%e)LUk7@b z@s$56pr?(1=BN%kc zM*Wy_o_$%d$NizzTCm;I;b{+Qt3&=coIRqr_Dnav&DEF1bS|CFvEVEDMoUxg=IYNj z`gWQq(wGl@ZMs&tk#=`E_%r|=a(br`=)=ump9Q1VhpT4XNDzRA7AnLLu>yyxx$?+h z{WQ2k-%tRVs$H5Q6uxr{SXx^ zBvaCbEm{4~85M9;9ox)r-hIxTLRmqc6DWxCfULgE1xu z({M=di8AXR#I}XmBI?=!TwWaJu9HU3h;t%ns0`ql`_XtTguZ2NU`>y+G5X;}1eY{u z^iz8f6T{1VHXzaGXBpguk<(@D&1c3y5U@bG^$FV3dkUCCF)Qq$(fWu^!>WVp2n~7d zfhE2c@_LCCZcdj9KW2kAI3BT=rH2Y+Mwh^Y5I?RC>iz=ka|4M-I58eJs}*9{PZHBr z>Qi|H;bezfjr7`+@Fk)6;bBxwavU*c;xc^d^kLk@Ceaa0Pt)2%W1+QY=~(IjpAPSt zh^2KPzgQ0B4Kz9ezG-Yr#!+4lX12X)0LI{G9L@3#@{ThOWE>E`W5&6ENEPP*c4%qpeppms-QTH`)p!HlyAetqA&3 zkR>x+#~k3KGo$g#!p;L_NP$aaRgo`2X~3p4qqA=|l?gnmmnY-}1ktZq(@jNeLZZ=c z3wAT$oCd1(81ECjGGL=OR3tM_j9Bj*nra~)t=AU>%bu}M8ml=8;cUy4=zadyEPIFo zi&sLhnXMWo z;D9mUTx>w(RE!2uJPb)ahh(F#Gq#Bqx$sBvwJtV3hO`=8!+FE&TpSo0snO(RC+6fQ&Y2-Dt?b-xLr~Y>BYLpyMWg!MRO~oAF@hzL^co{rmf+_E(m#1mqyPOFYkS1@yhhO(KX$S*_FYdtQp8<8;UuH68nef>rv z1bRF7IXXJiTinnId5kE2MW7jBOVFp#-13;4cGwWZ$W2*`*hjsppSelYQVju{MS3NK zOaopjXtgZx&4VftR3#z+9O#u0?y4 zRRAhJU~mza3%VXx!QRT)@=zkU>g86LYNY``hN8XR6if{X#YcomuF`B%c;YExIVlBv z(LmV(uYu5luW6P~A@UQoZ}=v4E7}(44ugJypv_PY2y&BhP zEjQZLBh}AX@qnB5I(i}^by9#=#H?>nFLdzRMmQupy}-fe5Kcj(T6A5gM=8;`8np&^ zjXaW(8Kc?&-*iP+rsYk8OHg$In^9`umyPbUc$e3#F;L++-BBT7SwV+O@W-nQoA{%Z zPp=+;#%bpij%gx4K3TbfpN}*YFi;wpS!JGo?knb zj>jZJ7oxq*PKvgm{Oz$2e5|8hGT)(+0WTDi^VhQGS37L9^J8=93G?iCFP9~cQhhv4 zo~iXb`5!bT#J42JbF`JPM*SEtwqcJR)Y<@R8ak>0?Xt=68%%&)8}8%~7Ea3)$Kuxj z<7xvmq*R`s@6P!K`R;_P5$vCy8C(Bn+3iblH+ve4XUb+@29cXj1AN&e_J#~lVQsDg zmJPWD-16-lU=^RF$c=~d$o|BBA9i|-3&<`SHLf&0Ziu%Ea@l3*+RkZ|;ii;tD_lva zZKn(Fqml-lXN@o0j&e=9BUIBLEuo+pwEE zn*5TKep>h5aqZ+&RCNE&$PEmdlWrUt#_^psBVptuu2Y-b*THon%J{l@RqB`9B|NT- zXPDE5FtCs6#rY7egDB|@o}^v3)1lFYGtAV5+B1`V9YZc=_qsHTD23Mbt!{$pvpOvC zSnpIvh#@xz9vLZ-S(0sTfW+b9dx0_R6btCs)0&f=&%idz!`Z_%68I~*1V#4tOC?2q zx>HZJb`%}rjB2%vxNNm+V=kGtMOamORN*<)U7ecbn1NBQY_MBgB}88@L~IU(lJ!Qo z%#3sW0A7hRW=yk%CHc^&Cp`nVB!Bg`dQNYv9#Y744n18NDo4HHRm0m|8Yvx!(l+yu z^{ndt;Z?YXJQJ6u7yE4V10T+EFlQSYhh9Id(M_>$L035rQ>5~eVHgTW#i%JN3A3l3 zL5xgQI1j&A`C8#-^j0XYU?>x^0{%v9v~sH!d-=2_ixSw_*C&oes*-*l8XLtOBql-P z!xTh9>nQ#}aI+R{8H^Ta-=H{-xZBYptZ}T8*cj!~0F!K?JrisS4`8>92WN7WvCo08 zQG}IUasP1`-IV{pB>X<4Q)P;kSl)(fRz!}UbXcu6c;xXY9+G*DD>t9RQa(B4QG+0;gvqaI(1zX6r-~@p`_N{Lr_`&|9&9;%tw_V%p z?UPWq-{Z00tg1KLJ@279YLYI*SOqHzVQwpz8OvwT0gz)OMCve_tn zD2O~ZBblwD8`^OlR|$ z9K$HLWUd*ruz=h(Bmao}i0{xX?T_2qeKf>eUj9p=s2k{G39LdUKy1o)61oc+woJkk`+(D0`_s~7r<*hzTXA}x6U}!^e$)R|cGDR% znpSWH?d4OM&hF`4r1Ygl5L-i*sJzNXU61#OC@h0C3ly4xpr-(>IdK$q0t(qFHb(o1 zMzlcE(AsZGw^1@t?+S~oN;btjv->}&Glia$B(CVCOr^H8DBJAjvEn~sn)blWX3)v! zxs8;9v_L4pviU|t8M%L#DDg$MP;ya5Vi(^!)Hz-`qKP?`S}CH zA2h#Js8COnPY=I?Xyy%3`J4>u=RioquE%Aivax{7pu$GT{F5gk^*`zln2A3Df5!V^ zd(_z;47MXc=R?S+fDM4gAyIKe8Onl&ydC`btEwS~=Y2Qc7>B#*h8x(;GquVLeww;6 zS^M$|R#}1Z zQf$#3ZA!$cVXKnTa3*Z3UN{H8^h470IMNjvTZkjPlX<(Y#W!hSXtDN zV1pO50i5HV7r17c;7@M%kI$~`YIk<0_8sB|!U!)GTGq=+{Y< zQW0#mClfRK{51#rX`(Nw{@+fOa9Y`aE zAgF|qRRDE>iW*2%`;V?u{h~r*~L7{4zTb=niYJ_EFKttEmi7uDeGi9}i@xCE8QaIYdb>rU@_I<^!lY>sK;lQ zybgyYk#H#ir{*Z0owV9-+`0OgD1F(}?he{}*7S$G9pQ7f6;!!5mmjiPLw#8-u%f>- z+S9gkrNs?5k34_r)P^l~^@6n|x@pZ2+*rdN;dbP_upQ8nFV(2j#yw@I-NrFVq)C|U zCl@P_Sr`@zF;N}ogc}UN#KlzMS3J08gbdF7?KA6w9nP@Ln+^vCVz&;jn;cSQ#V>iJ z;+E~@?0_wn8?epzypl(8LaS!%U^|vbRp8!(G}_H4*A@&rBXRt>InHWYxbqmaX)0$mY~DxR!3h` zuJmU8UgtSzA8|n-|0=8Ccb=dp_NctxK6iVOWdU^P$-)IVsiQ1Rzh42wL9%h_0fmv3 zlQAm12tn-j*Zy<+1etHKFi1s_z9$T z3CTTRSi>);!Y>yTN|d!^?pMgUBFd_O`cVxetXNPcAf=>Gd=HWayZI;_O6SScEF!wY zdc6khWAIQqR2Qb{&xA}7*P|VqSpD;OY}IPUQkWTL>kd!CKaOpTx%tc>RH&)9qaC1^Ace_U@hYdP`;W-_VOy zWC^yx)00PT$vWII_Jxomg9VE!#{tb5)FjB|Lq!~_C>xOm`XSTtD~n-Xm;n!f{WB<{ z849Nj?4A@37Ckaai$mcl9`T zZ};_VINKRK?{-D@dOiI?>x#`@@C$f`)!5f4tCSUD;0zEk79!P+^p9B!7@ff(UYrFL z(b~k)kzUz77P9-@7Q4+G4MaDOv7jjX+#bog&T8u#YzvjP&f49@(aCAH&gUMC?jI?7 zR`d^Qk}{oTolLUh=TLv+)1q##XL9$I6Wc<8WD09=oXbwKEt9Z6u==3(Thc9$a zYKY8&lfhB&jSgG7^`QP_;=loxR9#N1)8agEK(+C`lZGmTgmdbYeq{BQP1mkb4|8_lI!518l;WvhI!=i>atU%)-#n7RTKXUn@ zvu=O*jTh{D*ZvFc{1$O?FRLE9{E_3w9=`pony~*}`wrcCL1wbIcM`Hf5zd30y#TQs zNnjV~OJyhxiL&v+NZCQ62r4nDVI(RIBU7swW|wY1Dp`VEoq@4fzD=_JewAdodG(HD zSI>&yORBc9_K15$H=ESFEF;(q*|p(FYHV!n*puNXK2z<%pWtLH6$$@~Cf#BH_`@AqLL^@0izmvF zrh`2K+ChL_jj7H27A~BQbh=|-IF^cqT`v9OX_~1hdmz?7(&tf!Vj414<*pzSfOp>Y zoQ-XPa}?j}?D2-&?oj=UU6G&`jRb-xyZnAnXSdbr*6@w4qh}xgRy&14V15Wk0{bUG z55GJL{^}bRwBC_00hS1H0IPs#ZiE*XF(mKMMwss5=QVax4@f}v#Z)Q&ds$N4!_RcdpEgR(t2>UM}7F<*Y0Bu)43&iiGb%N835 zWqiIa)ovRLW&nb=<=KO`TS7ag`jeXf;O$ay$J9W#_dQ~;t$hTNv(19sDP-q*?V>e5 zFqD<7!=7YNWStS#*$&N6bf98On`daJK2}K>MPB4dP&g7WK#Q~p5_5<6FfbgJ53{d5 z`QeUk6osLi+il53_V176B$rFhb)J79I$HaWFYj+!hb-6$@Ig)^ zGpd+}KqS-^6IzC<0f%Xi6m;&(5*1V~NnSKOzV_;5?a}=9ne{3=cX)iL?~C!vw6)?d zF4;1rt({`t?TKsF;YTtCG|v}5P^E=y_}#m9 z-@R*^b?&OYxLB)|?y22ld7nPd!fWxpE%+A7uXqjVVQ#C;?U|k3^WyBDxw$>eP476R z_a?bOF+rqXk{>2xI))m?U=QC+r<=J#v)7+J8)5hvjq=)^^M?-2e}2uuK<)EovZ3hh zdy8~LZ!E64=Jy|*|Kj|?gY&QPgbl9*_O^5vVq+at;n5qTC<}81vOrpA2)71(VnK&} z_3{^~u3CXVXe>i_$aYzpT|dyPx>4vn6*R+XZ5t0HYaXLQ)=5k(tTwu8H@a!hs0 z^1h>mtQ|jgW}95`>LW1Fbh8J)+9al2__`XhxHD4QUccDuLVbH%P;?JU-d!GX*k!S6 zn#G-!5OGYrSH+xEK`|7^!|U$SPdiZ6T{!M7C=5acK^O|(+U)Rg>mR?vDXY5dwrUmE z?6zm0gM;VPv(@Tz20!ajOE9=-Clcf2}!N;skW1^f4Gb?^U%rh&g){dTl zgh+B2a7{Q=sj`tt6Ds1w!F4KZk79Zj49zi>M7NS_7Jl zZ7_>!=|CQ|-=YO!bGWtQP*i3PQ5m+d*}m~DignU}@zb&kRdhXCAu{IjWjcriQ<4-P z&ABzHZ8W0#9Ul8k|FkR*4;DyuZju!$xM>=-7U;5}OX(g?IN~`9fm`nIWNHd4u^yKO z$_CQ0`>D?+77WAM1NI@KbHg&kvJw-GxvhO=aAvwsa#-BZR+EE;)P>==#~*cCI|JUr zKbGRKBug*2T+yL)bdzYoFRsW=zmn(;tBH&;z}jyUosl882YciLU9lmrk_dE#?5Ibj zd7|Oy;1hFmiBjn*DIN^R^Ri-5R0*wIn(Jn%Y#Qrt%lu>-Bc)sgtQ15uK`b4prz*lo zhDnO$1cNfbU)UmAK=?t}>0Zqj58L{}4wu(5w#5MMoV8Jathz=}xzvG>hD!&mHb+<6 z7WC85XH@udQ3T7xvD~mk%%9>TI)z$=+NC)dY=m|ct1t4!555}{82b6g-pSr!@LXRc z(iii(-`*RI^m=11fH-VG)Z@18kM#CNhPhge!*qMdW(z6KD0|NA5rBKJcS%CmdwONMzNnfi6Tvn8b~smM7Q4fqq06=DA4zMpyT z6<56Xito)8de?fqw$8Rrhm>-yD6K-$jHJ4z1~aQfm#1Bn*xZO3NGD6#LGMhO?d_9> zvf+<&=UeS3XyA(Xw(=fU?mFgSnJEyD_@74BF_~k982m8c#lrt??OWjFI;;EU+94Q z^PO|P^PMy1Wrp0U@_RjFQ*g}C*X40fy4_I(y6|-8bZ9(wJ|p`fyly;C;&gDaz5Jp&!m>GTF?YBlAZdn)<-K4}aF`Xg}^zH|CGbJ#^&6 z2j@Qf3wUJ*-GK~ep};u?wk(Z#vz|P>MUBJv1q(mvN)gqKf9~qIH4(i!kx8bjJ8mM^ zj*ohr(c$iFq$?S5YAcn6KDV>^{b~l%mTKvz_PMo@@b(xTV#=~L9q#uy`zR0!4|+V4 z;Ua8O+UPG-Cav6Xb+SIDJkIn^&D1dR;%k`Ey*aCsjjRjmLYtDE-7uC9g7e^d(_! z>I!5AdJ`}wGiweJYOG)41^w!mJu!Bq76t>-{0GC>*^MAxA%7qh_XmTCa0pDZeNGwX zI3ZSTIAO8<5O-a)#rPJ^+ukUE_$z71fKGVa=Hv?Aa3W_jh}pp@=Wyi4InVr-H6XC>_Q& z6q5v;4{3*X27}4Wa6YWvu+tw%_73NN58M@;?@(W~&!r9a_3CbCcg&AzUyB5&Ki2Qm z3VpDxKrrC;vi8|MJ*YA$+QD-*i+(Wwt&U6n@1k_rn4&Qa-U5E3Bzw+_I$K?H~r=EK5 zDTrVAoxQIxy=lD(?jF@RYuN^y-MRS(m9eqP*wgA;UiS%Hus(Q!jfQbd^Fj98XZHd% zt`r=+(g+o`aFm_<`joteF*Ns}FBC%Pw@_$pj#K>gDK-Dp=C_^RHb1|uylpP*>+*#^ z8;XVSIcOffYqsO${QTsZ$@!g=zEIR>8|w+^2X{gT0zGVkU9zIzV+708g8py1X>Gfv zrcq|eTaQ){enc??){Of79ory{hrQ!l4=lT&I&-?b`wx#~(ZyWh0lhK}U8)NXbISV{ z_HG^Xcr^-nvLg$#>@+B%%JjSS)2HjFX;)B-M0{Exs|BH*9Q$C9;_k4^-%B2MmvhVb z5Y9dLd0;^6qds#S{W*Sjy53gzUevsbL12x3FKf18i!cV64dw|L`6^fn_!#MyHLuJ4 zspZ}zh-~1#3O(6QT9M-T91UMVrjsnd=9Xem^#M&&+)te3WWUPWeJ<=xC zAr@5Y)_I3ry4#RJqlo{q3Fqo$JbMQAU74h3&)G{?BJg$84O^Ra*3H$(e)cT?{#8nV z_PjSge|3`J#{3w?YAExcw{I@V>yZaNO8(;*gQoh1L*j_HkpzT!d`j*>jxWC+)JO7qr-tFFRyQdmC-FOPDpd_f=&3JGtYm+xO#88`TYD%BH>IR|p$EGCt z6WbtvNXe1U+r-UsO9)-H%cj>`;-?Z@vE-u!%Jy+?D2ehwo8(z&NugHDB6++KtQxX*E-9PZ^VjVEfe3~B)PW1-*Eu}ww(^b2D1~{c?EK;Jrgfni8R}m zt(Pw+)!KO$ERt`^;bmo8d;XrfgoN9ct*=}}((R;4+n|TO03G)HOfs)M$6tEAlCQlu zkG>88*xom`vEF)9&HppV!S<4#`2yUm!8U-%_=Mf;(Bn?+}mI zYqf@rGGmk6CK%~PzH|XmndiR^Ihp7DeuoZ;>jJb2#z`zMv$PB=jLq`0y_Ubq zLi4XfW`4#~>yVo5Su$97Zm{V5OzKxIAv-huRPon(-A;SQuv-nWQaX{qUGOp|+eE@R7J42w;Y}rZ zN4&Wl3L`R`DBi|CZS9b*@4kQ>-p(pDUuqM!XWPVaJMHO$eZ?g53ok9B%pA1K)QOIM z+Tt{>P)w)$HVfA_p>pOyC1U^O6-w&gXqUCy+QU#H_j8w&)$Q%%M1NCJ71;#8vzX2c z%I1)<*X-S6>t+F+V5#Y_=j*(qLr{0XuG%5%x2z8+W0h$ETFy6O=j{pf8mKSFz=dL^ zRV}x2*We{3Y$3p!l4%w0R>3ONBZ#iXKOkJOHmG$TJ+?iy_RdD@SUYFOIXW+N))9V|j5U7an({wV<77=G z>-7e3>UChCCikg*W9lWmR}!D6&7EZC320WhwFi-D1J~6wIN?2zS9taFcE>%iMQ`mN zqpkPb-dX1nz;{AIUGWbp`IM5Cd(;FW8Jhn zuzeq}r8%TTqIdHZu6*?twdWH%KY6nSs!4qr`dMYmVE;C^3|{&QTQp1B+?RWtF52JzC(pdJn)DgBCW*4=a6f&!uo`*pKI&U1t z$b5=$D%-T=aJ*b(_11-6NrtXp>Qjf!KGy|r?}T4c-UWld`kML`r<#pTFW^l~oBnwF zEI!h94VXXZhGF2h+>V?=g$glj_Q_wW}z9!jOc&A;$?mTW{pw+I2tSbtAHL_Zz?_r$%{VUj~|DQt*0 ztv2QGD!fGmMnqW-vM95way-Md6vphZ9YCBPw%{|B-a5x^4Zw?IIN-iBHN3^#ACaKl zD#|I$Aoc&{4~6_s^tsP)iOwLHsyo!mS~yWt z;Z`YMM^Spm@HJ7@vWTpwN{KP)P(CEq)X3y`o=n~Lq9r>fT{mc&U1M*j;^Q{P0j+_sq^<5 z>P&O&6`r7insQWOnWju#i-^rLuPO6kEd1rMvWadEh7rm(+{e-g3@6c{cN11%*^E*x z5zER6TODKQt0hIXRR%j1@!=@Sf72Xg&{1 z1CB@U$&6(4`4h2ZWXqfSL$qV?PXi@bmdAr|#iO}1*`ev_g)Q*q;l$tzjULX$x(F-6 z@806nyb+_{9dvq=U5Ty)#{%n)ba$sbunF`If3K@o^G}TgG;d~l0V-hox|t4M7VAiu ziYn+rQHr>R0A<-0Pl z2k##lOVjkwcxuZ^b+DkPutRG4zUleyaBADu7%Ww{I9=FD)Dzu4Q2K$G#f!!f5+(S{>;SgQD{PkZ_tZ7jw7BK4f3dLzS6di znl&jNCQc8=%^uqA$;ZdB06q)@Zw#IUuWo~=rLok{22ofy#)OLzg_hUNA*D)nomHG@ zy014CzS8p0Z>jREsp4d|5BA1_Zp8g)D-aoGf&SRnS)k@CTp%cX!GSQXBU%Fu->UpC zz+gnt>onVtpaEdmph8~4=-D|urIm*wS{1v^hY{}@t;hQ0k6`0NBGRl!V4sgQ?W8}n z9>NNDk85%^IW%x`y+`*^jXXmu0dzfhiE&{OJ~Bo|EE?|a4)-s<<>rLXmtLq&^lj}% zt0aHz?r7hkk6b&mm%O3gu6{+kl+R7rRyIJLqrz#n)?i!0kCNV)h+zg%<54l|idBbI zgu0^W8oqe)m${~#W;+S|0yG=CC7)WhI^L*S6Vt)b^{(@(rGw?!QYPtT?q1na2sLVJ}_zny;mBR&9%zC4ytC}+EjY*6XUFLz5W0mPx zRU!|;#R6RKaMs~|0hU(7Loz|CP3B41hai$V+ubv>$x{L%oS8B0Rqx}lj87u;ZqDWN zxlRW&fxGiaxep;C2_4Th>p32%F^Fl$LaERSN6=c~2sFXaF*INdH!D2Zeb;kr*6n^I zlnlRZr`z-9aKH%%OxO&x9!?Yv1$%;firo2%dtB~N@K6Dhtj>2Eq@%y-;xFq4$Zjli z9EZL^^*7EXyOUvNOnMfIW<}@&Wxxc;rd7oxt|Iv8MXult>}0?RJ{&>^{^JM3+$HPi zVz7bcuNn%Y+J(Q679p#ZYJO&Y-O%+0WD0x?aOCoYu97&zHC*CTzJ+7w86H^I&pUJ$ zCv=0$Ei%}K^yNS>LS|rju2CFo5skxw5%1gKqu$PB!6iHp1IBT z+ZWQI2K!cTpqn@y%aZ3&v>@c!QO*P#aWY?-wnQdVK!Rl>6O+>Tm}OpL<(R&|@Zz~e zgo3~J+PT9y9{6kg1qP7be67nBI`-_TQ_r4y=flU2J$%a}i|1Zkfa(1kzH{wdXdFZV zpHaFN!3%TrB`Y&_+KMHd19VCW+->TW_YDpfCY4<4*_;sM^jFqTe&`vYZ&5X``^UlafF2=uP0 z^e88tNQ_}+v}k$MU&uw$<#(qiB7Uz{)31w6q-&aYVviP{@o1W7Cam3FJL? z^HuybgG6ioUPnE(R{N~_X!W^{KGvG3P1`ZEhkfKdx50DbgDUd*ni?zZJ?f|ISMfc+ zp?sN@%}$S+ceVUCO(4qWjLAKynT9EvVh<24jJKy@{F~&?WR|93uQl#P>y)c#0x$F? z2;o@-OGI^^W<1S?nu&rdD?xQmhS!goiO;dm8A~GTdi~(R`qqO~dCcrbk462vcl)F5 zXGe}a_89)((%*m0AI{B<@VqMX&n)bf_Mp#?XUiCJidTRQf6hCMpaFT1tXy|qu?5OK zCUR>s3GpgZGiDb`?rIwD@aVMzIq!AZPtym=Dtexg;MFY zCn(^WnB<{R+5ti5mt7vMw^Hfl08j52hMy?hpyc2QmzS@4kJf6n+iwRcUjvO@13i!P zu37A(?3T&VoGi-;)LfPs8PqTc?nTZt4%vnYlYMGxC1|%0erg?oxQjaS3a?z@_M=}9 z!0S#x_miXEb&c0#CB4^l(d~E~TxBc_7T%u7>HIYROwWbUEo`#?hQ4+Q`dWOvVU%SZ5@LoRbpQ%rzeD{whownKH5=BuV~c2pg}&=|~{T$Kj<2kNKlZA`=b zk(bwAR+pI5!H4T0*y2yEk;(p7&D{bwa7|b~?>f{~mQU=G6nqrwL?O1Fx8M_x4W3J> zj$_THou=N6=})EL)X8zSm!@P@d|&oKQ_G^q6~ zh>;5VU6$uL@Hec4AqyzKRYYvxnBj>NiQ5TCNXnhZ|yRipJP+bJ&U;2d#VH|qFe)(vzJZ2~@# zV@hlRui_wajr9Kp%OJ{X^w|jxKSnM&QUYaOK@Z_U7TwSqjhB3;H*P)U0+*M zB{2^Tz}n_3)>-pt09E60Fi+4>JHa?&+VEPcYv^*@G}e}4(`1>(vUVM{!Fp+ZLr)DU zazjH+rkJ@J2>bov=3jyInWG)*Y^8&yqivdLL+5<+*N8dJ`DcM48QN?^wxNeEz+)lG zaD|#^OXTPA3O~Kr$F6h$X{<}7RZ7345_%&{%u5ZIYcTzOF31^8V}-uxu?Y`k%7Zr1(GOVn+=qTp#H_f?y}{lYlonQLDmdFpcfq6y^NGDUF4l&4Sl=Xs zyG`r7p<>14F6vh67zi{!#foMqobhQ_H#f{|PpEdNnI~IB$$XR_Pl59>`*H)6hIQvl z;0$kcuwxtcI$+vXqaU#=oQ)2SGGUX2*~8l-qu4#6!T#M;osKCoTPd(urXo6^dzihX zt~z@I-#=|>j4z~CpV+f!v^1JgMLdW>yQV8*$#ix@Wn4E4XtW)$uN4~geEw}pb=+7( zlYxTYB=K~2wY0<#saQ))a~ga-z>Ig84M{{%%x0NvEeAa@T%+NtD*P7*y5lKV&<$TA zH};N^%M zS89AUwcoDFya8HEQC|)*f$Q$)1+1y2K*(G%g(DC;l3498ENlp5d|<~2-kKcfCn^LzB6)2ER!!z4<$ zZiExJLVLLrTSu&AL#zdnqjb$$&F_WiVvcB@{|xX#m?k0Bvd*%E8PJhP+7y=Q4t*t! z53{$AsAdWvW^N(erib@v>CMVXaw7I_Fc#EWipg#MIln6&$4dCc)smDw;6qINc7U8G5yc2W4ua@ z;_Fr-f@oOdm3cL>jT5h;E$yKh9))yjXos!K=wz^}sWS!~-6osF;u!_(y)N42NgWxHyST~3b{8+;(quW1IYjR(JTfeJU_N`~<7K)Meo?Hm|2XEA-(;I?c(>(n1@WcQup0!8LCG1Yx zjIvw7JS&15sG@qZuGDr7wU?~9rnGSp6;ZbVRhUm{>K4@_G^3PyA5ojfcBmVVbNJi_ zLbCpu`dy!TfSb&&ySqZooy9(1PGi^nuu{ccLRt5U(&?d&Hs~397h~_;`?wd0OxDqd zu=oI#H@hb(7h!I~3Vih-IA3t|<|$^qR+{Rd#OTg7)uWMMfyfZf%V@5*B=)~)G4!m} zFfW_JD}G`Ax(?!9GX;VZc3x6gN)!cuA;+(RYVaQ+-79Gg^JG=hPOMtKPtq7s1h{l74(}K@FP1h%g_(d#js?deWRztT zY88^>UbN{Opaa>Dojun<6LJt*?L*Lq+z4x#BhZT604r3HcDy7E7%ZqA(oOL@7pb>Y@ag=Op64r>TcBa3k19{aEm1y||7I+*ul?5gMg2 z8mFx^L6fi(*iJiOH#SW<%2NS8OG;Fx3Y@;qU`2K(>>}o99?m!SkUwKOLa!=z2Pcg_A>c1Kmi6=?ERAo9JeG6CI;l=vKN7>cbk{L3h$!bT=KR ze?j-qy>x=!O!v`AdJCPR`{^|OOFBbm=>d8x{T#iG9;Aop?eq?MC;dFVi{1@Q=)>@G zdXC;hzetbJFVTDHQF@FXr(dQg=vU}p(UbH(dOtlyAD|D?uhP@>YxE&{hCWQs(!Yk? zz^}urZq(7l=AvVIF(zoe5 zh%5Hz^fG;y{xkg-`mgjo`V0C?`ft#Jy@D09zoP$6|AYRT{)YZ1{eb=!K5hRu{T;nZ ze~&0&KcxRdKcau2f299QKc=71KhgiA*Wh*Rr|>Wc&t2%c8e-JCH0;dMyqZt*YXL0? zGl#dHIqkL5@W^mgHELV?~ZtInKy&R*nmDT$JOI9GB&|BF9zpn3sIy z1wSwNdBM*MeqQkNf}a=syx`{rKQH)s!OshRUhwmRpBMas;1>kHAovBrF9?1?@C$-p z5d4DR7X-f`_yxf)2!28E3xZz|{G#9&1-~fxMZqr$KBE4p_9_Z~QSgg`UljbJ;1>nI zDELLeFA9E9@JoVU68w_jmju5g_$9$d%vhC=lHivFza;o2!7mAZN$^X8UlRP1;Fkrz zEcj)?hw@b6lm)*m_+`N_3w~Mf%Yt7P{IcMe1-~r#Wx+2Cens#rf?pB*ir`lSzasb* z!N*de%3nqBD}rAU{EFaL1ivEq6~V6vepT?Rf?pN__KmPEBLd5KP&jNf__KmPEBLd5zaaPvg1;d63xdBO z_zQx+AovS{zaaPvg1;d63xdBO_zQx+AovS{zbN>Ng1;#Ei-Nx>_=|$SDENzlzbN>N zg1;#Ei-Nx>_=|$SDENzlza;oeg1;pAOM<^7_)CJnB=}2$za;oeg1;pAOM<^7_)CJn zB=}2$zbyF6g1;>I%Ywfw_{)O7EcnYNem*D9t;l;<FIA!CRJmu3GJ3j3}& literal 0 HcmV?d00001 diff --git a/lucene/luke/src/resources/img/indicator.gif b/lucene/luke/src/resources/img/indicator.gif new file mode 100644 index 0000000000000000000000000000000000000000..d0bce1542342e912da81a2c260562df172f30d73 GIT binary patch literal 673 zcmZ?wbhEHb6krfw_{6~Q|Nnmm28Kh24mmkF0U1e2Nli^nlO|14{Lk&@8WQa67~pE8 zXTZz|lvDgC+Z`3#dv5h=E26FfcG1 zbL_hF&)}42ws10s6^G;;cE1^EoUR)U5A70}d2pLv!jVIT7j&Z~EblI3x0K*v_sV|m z0kj3v921Z^em#l`(k(o@H$3ZdDRc@9NidXDNbqrumReCGv$gd8+e8WW28HVqkJ_9i zH>s*<31KtHjANIPvi2#*6BEu%3Dak5O_t&NBI)H?V$TxT}#l{vOTn5naXTfF^&~Hhq+NX@#Ccc>y7T?;vjI&jdhsDsPJyAw*m0Qz>i}K7# zL9w50Ng{fT}A5JUe8lRK1h7_Y2;BWJDd=c6f&i?Wv5(5q?6|P zQw{>maxZP<537OA37Uk}7@%_$4o$EWe_Zl>&#id|lE-BpDC#+Fn|msJ%_2h{Hg1vP z#N8WAzfWasG}yq|xqE)DrWaOofX=z|?*pgc%{ig5vl!pqDlC|q&~Z0$&Rvsft&VO- z4MZj+%-+Vx%W}v;V76hyp=;+R;x+~t^Q%*xuFTQAF2})fSfTHDAs>sO!OBw`)&)o$ c0!CNZt))x~rAZP^^P&YOFfdqy5)K#u0POD40{{R3 literal 0 HcmV?d00001 diff --git a/lucene/luke/src/resources/img/lucene-logo.gif b/lucene/luke/src/resources/img/lucene-logo.gif new file mode 100755 index 0000000000000000000000000000000000000000..0317bbb1608f96bb79dbb9b3d278a250662f8585 GIT binary patch literal 1337 zcmb7DeM}n#6o0*z>$MChtR3Ch*kDjfS6RhraA;CKTA-F%J_>9rvmintNIEyU&MnYZ z24h1h!vrL_^0AeMzzW0{}i_2rt&l~t8|VNAL_t*WN{#f0ONt})A(ZN<96 z*QF<0ZB3bZvcl5SqjPr#CVLw?46qws0h|XEFjy+xz%>h7cyHAP-bpCt<4Eqr&xe14{JxV+QX&H_ceC0+kKL)b8zXrO zSg?i?^#S(rHgVq?$bN{H*eBVS><`4A$^9b)_L-0#e{6){=o8n!wLqxm5AR*H!QQ** zh?u+uM!-n_84m}+M^EU2`~RBWN+@O1SCX9Hdkf!-=P(gZX%v7+5Ry6MKLMBrBv-kA z71udFlcDK5}0by|Bg)SCCr3JIK=BvZB6gySr9@L+>a*fTq7A`DuikJ>|b6o1c zXymU~nveH8I(}NdKNA*zLu=;rV;p}d2sEfn2xCuPxVn47xaP+eST$WL6BQK*Lu~J6 zC;xe!pWI&fZG@dR-%+@Js9++=-N@tf3xdpD*$x9|&~*TbTVW#-K0D?iliB`Ci)rKQE+Z1;%yrf? z#pYnc!|o%qXo&i&2lYP`P9Bzx2Jn=S$ni6xF}bvfGHm+69P{EgZCP02ewqgTM`8mi z_cIZthH5kK*O$k#3}QvSw1p9-a8UdO%PTo_ez_x@d2Z@`Cp{+9j4=J)GnXa{JL?>b z^WVMf$N<-;eGKaI7ly`2)}!Q`Mgcf17b{W?WunsP^1L%IxaN`8>iTSJ>sWP2-;ECh vUE+vT*BR?g39R1Qiw(6fPt5O&S{wNMe#P40NU7L{MT6nkozPEMEJ*whbcB!n literal 0 HcmV?d00001 diff --git a/lucene/luke/src/resources/img/lucene.gif b/lucene/luke/src/resources/img/lucene.gif new file mode 100755 index 0000000000000000000000000000000000000000..b4eeddb3c382be75d7e4b6432b4ec5dca1a5e70c GIT binary patch literal 335 zcmZ?wbhEHb6ky-$L(c>HD6`K~dPl43Qo;R4oeEG?kNe3@Jq~y zE3C+qk(1WZ(~gV@&&)~x|NsB{_wVoDzkmMx`ThI%uV24@{`~p<{r&az^#uh52?+@S z0RavU4h*CN#h)yU3=H-RIv}keKQXZ79GG8V;ZkZfwXN+&g^YS%Ytk+c!FpD{bruWes4cAt l7U*-8UPY6Y9{~y literal 0 HcmV?d00001 diff --git a/lucene/luke/src/resources/img/luke-logo.gif b/lucene/luke/src/resources/img/luke-logo.gif new file mode 100755 index 0000000000000000000000000000000000000000..4ec2fff11d8ba902df7f7285b597fe89d794f81b GIT binary patch literal 2408 zcmcIjX;>3!68;i$bRtKh9HKA~Kn|5d6gfO51Op);1|%G-GC@h?5?Kxtfq_O7!7<1s zA}SIIX9VQ-+STZyBCZl$Py|E-6*)v^1p#q}?F@|T-~F-mJk?dz-`nq3Z`IRo?ye4w z!H<9ymY@LI3OaJwv=KQ_Ud$zbbFVjs&(DAS`0>@NSMBZXw{G3KeED)wQBh`QCJe(VDJjv>(Hstk#bQyZR2v%`LqkIn zi3I+iMn}lcteWEkfCqwQc%ZBm6g>F)9tQv#fU6@oFc~t(pRk$XBfZbJBDHPCSH2QU zM9xwupO{~ zJMaNJkZL>d2Q(yl0ygkKx>?9vkjx8lOXSQQxFMr=L!v9f^T~7;>0Jed%AMgaU*P@r zoEXLdb3pi3f1j8r#eHW1AQs>N1YYxI_?+Iad}-7S|2*Q*qDJLRu#5Of^w77Dm6DW2 z=<>NkzR>1xjNfDEKLHXL#UcCvXq(RQ7ssHy4;_lkQ+coQet^tbIe#>Q{nM10`Sp54 zHG77{CReep0}WLJq4db&t%~ z{^5n{d}oMI5v$QlGJ^XaUw_~V=H#dkgROXz3f;|Dg!d8;3`RY+L+>~NOd}3*uH2YL zt2LgOFH(I`%)6yik#btzGt>EYUuSHo&AO;Jb2D3DtV9>2i~o&1uoiVBH8oEj^t)wiu$yV;24|*5QyvPh--XWwhkvE#3XRcI zdMb%e8iO-_Q2YE8XSO<|!`r^_V)!%1Tvq(*qisySFUuItjMpuGkOA89(@(4GKjIHPxyB%g!v zSzKN+cVf)VXM~$`wY^yM!A%o4YMBq#l8n>2Cm)i}CIp65RC2OkjN!B#1gSTyUgsAA z=0N2bPq*Hp7aMK2DbfXZyXE#xJ*-Tp;k|9+PMlVHN5A|IFPuzY835P0igmN(=v@P5 zxu91|Vk7lu69bM<;*At+G$1uM5*hROrx@)FnVnRvR{xB9 zLy!ngQGLCM`VXUFnLn_;Jw@$JAcGfegr6Uo9FV7;ssO|iM?uVb-09&y6;{n{+EGF{ zdqh!lPX}EyE4r4NW%qaT!JXrDIcj5?{+Cx)$Ef9nuO7LV3#@gs3Uc)@eusFCW+i9q zv(kK{VxTUr_e(XcNB+m2d~}LqFzU{1?wF3xs_fs}(;W;I?V|rid#Azak?Lo|gS=&1 zZv_a^=XWv^XJ{}l7`CrJw+BKuSVG(gigb7fWHqzw;x?AGlLxe;*Tp=@ScD?O;X8uRh#{Gj!%c`?Tdf+mIdu04BlIQg>*l| z(j+I=DC9c&s)iGJ6FQ)4@B(I)H*ZO0dgAqI`?tS}JPDeFgNOK9_%2r~ zW+FF3^Kf3+X>37~MHx+KYf&YXpV1>T2da$bcht`Z263oPAdmBN2O-bss6P2*X2iuZSI;9IU&1O5m>h>c ze9qBo(km?yiQh+R*21xOG?IOicK@@=+@YF zUs##o<|lm-duj`ok)!hY*o|@F(BU}d@&gd3sfBjh%PvuE^bIb67xcLyw=TmP6{~&f z!gyoF_Udr|OZin-PpyTG6}&)?o^E<~bMvW(=HaWeDg_pt<|}G7(P=kyF(FtCN9!@p ulSfuKO + + + + + + [%d{ISO8601}] %5p (%F:%L) - %m%n + + + + + + + + false + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lucene/luke/src/resources/messages.properties b/lucene/luke/src/resources/messages.properties new file mode 100644 index 000000000000..c59924a31862 --- /dev/null +++ b/lucene/luke/src/resources/messages.properties @@ -0,0 +1,268 @@ +# +# 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. +# +# Common +label.status=Status: +label.help=Help +label.int_required=(integer value required) +label.float_required=(float value required) +button.copy=Copy to Clipboard +button.close=Close +button.ok=OK +button.cancel=Cancel +button.browse=Browse +message.index_opened=Index successfully opened. +message.index_opened_ro=Index successfully opened. (read-only) +message.index_opened_multi=Index successfully opened. (multi-reader) +message.directory_opened=Directory opened. There is no IndexReader - most functionalities are disabled. +message.index_closed=Index closed. +message.directory_closed=Directory closed. +message.error.unknown=Unknown error occurred. Check logs for details. +tooltip.read_only=read only - write operations are not allowed. +tooltip.multi_reader=multi reader - write operations are not allowed; some functionalities are not available. +tooltip.no_reader=no index reader - most functionalities are disabled. +# Main window +window.title=Luke: Lucene Toolbox Project +# Menubar +menu.file=File +menu.tools=Tools +menu.settings=Settings +menu.color=Color themes +menu.help=Help +menu.item.open_index=Open index +menu.item.reopen_index=Reopen current index +menu.item.close_index=Close index +menu.item.exit=Exit +menu.item.optimize=Optimize index +menu.item.check_index=Check index +menu.item.theme_gray=Gray +menu.item.theme_classic=Classic +menu.item.theme_sandstone=Sandstone +menu.item.theme_navy=Navy +menu.item.about=About +# Open index +openindex.dialog.title=Choose index directory path +openindex.label.index_path=Index Path: +openindex.label.expert=[Expert options] +openindex.label.dir_impl=Directory implementation: +openindex.label.iw_config=IndexWriter Config: +openindex.checkbox.readonly=Open in Read-only mode +openindex.checkbox.no_reader=Do not open IndexReader (when opening currupted index) +openindex.checkbox.use_compound=Use compound file format +openindex.radio.keep_only_last_commit=Keep only last commit point +openindex.radio.keep_all_commits=Keep all commit points +openindex.message.index_path_not_selected=Please choose index path. +openindex.message.index_path_invalid=Cannot open index path {0}. Not a valid lucene index directory or corrupted? +openindex.message.index_opened=Index successfully opened. +openindex.message.index_opened_ro=Index successfully opened. (read-only) +openindex.message.index_opened_multi=Index successfully opened. (multi-reader) +openindex.message.dirctory_opened=Directory opened. There is no IndexReader - most functionalities are disabled. +# Optimize index +optimize.dialog.title=Optimize index +optimize.label.index_path=Index directory path: +optimize.label.max_segments=Max num. of segments: +optimize.label.note=Note: Fully optimizing a large index takes long time. +optimize.checkbox.expunge=Just expunge deleted docs without merging. +optimize.button.optimize=Optimize +# Check index +checkidx.dialog.title=Check index +checkidx.label.index_path=Index directory path: +checkidx.label.results=Results: +checkidx.label.note=Note: Fully checking a large index takes long time. +checkidx.label.warn=WARN: this writes a new segments file into the index, effectively removing all documents in broken segments from the index. BE CAREFUL. +checkidx.button.check=Check Index +checkidx.button.fix=Try to Repair +# Overview +overview.label.index_path=Index Path: +overview.label.num_fields=Number of Fields: +overview.label.num_docs=Number of Documents: +overview.label.num_terms=Number of Terms: +overview.label.del_opt=Has deletions? / Optimized?: +overview.label.index_version=Index Version: +overview.label.index_format=Index Format: +overview.label.dir_impl=Directory implementation: +overview.label.commit_point=Currently opened commit point: +overview.label.commit_userdata=Current commit user data: +overview.label.select_fields=Select a field from the list below, and press button to view top terms in the field. +overview.label.available_fields=Available fields and term counts per field: +overview.label.selected_field=Selected field: +overview.label.num_top_terms=Num of terms: +overview.label.top_terms=Top ranking terms: (Double-click for more options.) +overview.button.show_terms=Show top terms > +overview.toptermtable.menu.item1=Browse docs by this term +overview.toptermtable.menu.item2=Search docs by this term +# Documents +documents.label.browse_doc_by_idx=Browse documents by Doc # +documents.label.browse_terms=Browse terms in field: +documents.label.browse_terms_hint=

Hint:
Edit the text field above and press Enter to seek to
arbitrary terms.

+documents.label.browse_doc_by_term=Browse documents by term: +documents.label.doc_num=Document # +documents.label.doc_table_note1=(Select a row and double-click for more options.) +documents.label.doc_table_note2=(To copy all or arbitrary field value(s), unselect all rows or select row(s), and click 'Copy values' button.) +documents.button.add=Add document +documents.button.first_term=First Term +documents.button.first_termdoc=First Doc +documents.button.next=Next +documents.buttont.copy_values=Copy values +documents.button.mlt=More like this +documents.doctable.menu.item1=Show term vector +documents.doctable.menu.item2=Show doc values +documents.doctable.menu.item3=Show stored value +documents.doctable.menu.item4=Copy stored value to clipboard +documents.termvector.label.term_vector=Term vector for field: +documents.termvector.message.not_available=Term vector for {0} field in doc #{1} not available. +documents.docvalues.label.doc_values=Doc values for field: +documents.docvalues.label.type=Doc values type: +documents.docvalues.message.not_available=Doc values for {0} field in doc #{1} not available. +documents.stored.label.stored_value=Stored value for field: +documents.stored.message.not_availabe=Stored value for {0} field in doc #{1} not available. +documents.field.message.not_selected=Field not selected. +documents.termdocs.message.not_available=Next doc is not available. +add_document.label.analyzer=Analyzer: +add_document.hyperlink.change=> Change +add_document.label.fields=Document fields +add_document.info=Result will be showed here... +add_document.button.add=Add +add_document.message.success=Document successfully added and index re-opened! Close the dialog. +add_document.message.fail=Some error occurred during writing new document... +idx_options.label.index_options=Index options: +idx_options.label.dv_type=DocValues type: +idx_options.label.point_dims=Point dimensions: +idx_options.label.point_dc=Dimension count: +idx_options.label.point_nb=Dimension num bytes: +idx_options.checkbox.stored=Stored +idx_options.checkbox.tokenized=Tokenized +idx_options.checkbox.omit_norm=Omit norms +idx_options.checkbox.store_tv=Store term vectors +idx_options.checkbox.store_tv_pos=positions +idx_options.checkbox.store_tv_off=offsets +idx_options.checkbox.store_tv_pay=payloads +# Analysis +analysis.label.config_dir=ConfigDir +analysis.label.selected_analyzer=Selected Analyzer: +analysis.label.show_chain=(Show analysis chain) +analysis.radio.preset=Preset +analysis.radio.custom=Custom +analysis.button.browse=Browse +analysis.button.build_analyzser=Build Analyzer +analysis.button.test=Test Analyzer +analysis.button.clear=Clear +analysis.hyperlink.load_jars=Load external jars +analysis.textarea.prompt=Apache Lucene is a high-performance, full-featured text search engine library. +analysis.dialog.title.char_filter_params=CharFilter parameters +analysis.dialog.title.selected_char_filter=Selected CharFilter +analysis.dialog.title.token_filter_params=TokenFilter parameters +analysis.dialog.title.selected_token_filter=Selected TokenFilters +analysis.dialog.title.tokenizer_params=Tokenizer parameters +analysis.dialog.hint.edit_param=Hint: Double click the row to show and edit parameters. +analysis.dialog.chain.label.charfilters=Char Filters: +analysis.dialog.chain.label.tokenizer=Tokenizer: +analysis.dialog.chain.label.tokenfilters=Token Filters: +analysis.message.build_success=Custom analyzer built successfully. +analysis.message.empry_input=Please input text to analyze. +analysis.hint.show_attributes=Hint: Double click the row to show all token attributes. +analysis_preset.label.preset=Preset analyzers: +analysis_custom.label.charfilters=Char Filters +analysis_custom.label.tokenizer=Tokenizer +analysis_custom.label.tokenfilters=Token Filters +analysis_custom.label.selected=Selected +analysis_custom.label.add=Add +analysis_custom.label.set=Set +analysis_custom.label.edit=Show & Edit +# Search +search.label.settings=Query settings +search.label.expression=Query expression +search.label.parsed=Parsed query +search.label.results=Search Results: +search.label.results.note=(Select a row and double-click for more options.) +search.label.total=Total docs: +search.button.parse=Parse +search.button.mlt=More Like This +search.button.search=Search +search.button.del_all=Delete Docs +search.checkbox.term=Term Query +search.checkbox.rewrite=rewrite +search.results.menu.explain=Explain +search.results.menu.showdoc=Show all fields +search.message.delete_confirm=Are you sure to permanently delete the documents? +search.message.delete_success=Documents were deleted by query "{0}". +search_parser.label.df=Default field +search_parser.label.dop=Default operator +search_parser.label.phrase_query=Phrase query: +search_parser.label.phrase_slop=Phrase slop +search_parser.label.fuzzy_query=Fuzzy query: +search_parser.label.fuzzy_minsim=Minimal similarity +search_parser.label.fuzzy_preflen=Prefix Length +search_parser.label.daterange_query=Date range query: +search_parser.label.date_res=Date resolution +search_parser.label.locale=Locale +search_parser.label.timezone=TimeZone +search_parser.label.pointrange_query=Point range query: +search_parser.label.pointrange_hint=(Hint: Click 'Numeric Type' cell and select proper type.) +search_parser.checkbox.pos_incr=Enable position increments +search_parser.checkbox.lead_wildcard=Allow leading wildcard (*) +search_parser.checkbox.split_ws=Split on whitespace +search_parser.checkbox.gen_pq=Generate phrase query +search_parser.checkbox.gen_mts=Generate multi term synonyms phrase query +search_analyzer.label.name=Name: +search_analyzer.label.chain=Analysis chain +search_analyzer.label.charfilters=Char Filters: +search_analyzer.label.tokenizer=Tokenizer: +search_analyzer.label.tokenfilters=Token Filters: +search_analyzer.hyperlink.change=> Change +search_similarity.label.bm25_params=BM25Similarity parameters: +search_similarity.checkbox.use_classic=Use classic (TFIDF) similarity +search_similarity.checkbox.discount_overlaps=Discount overlaps +search_sort.label.primary=Primary sort: +search_sort.label.secondary=Secondary sort: +search_sort.label.field=Field +search_sort.label.type=Type +search_sort.label.order=Order +search_sort.button.clear=Clear +search_values.label.description=Check fields to be loaded. +search_values.checkbox.load_all=Load all available field values +search_mlt.label.description=Check field names to be used when generating MLTQuery. +search_mlt.label.max_doc_freq=Maximum document frequency: +search_mlt.label.min_doc_freq=Minimum document frequency: +serach_mlt.label.min_term_freq=Minimum term frequency: +search_mlt.label.analyzer=Analyzer: +search_mlt.hyperlink.change=> Change +search_mlt.checkbox.select_all=Select all fields. +search.explanation.description=Explanation for the document # +# Commits +commits.label.commit_points=Commit points +commits.label.select_gen=Select generation: +commits.label.deleted=Deleted: +commits.label.segcount=Segments count: +commits.label.userdata=User data: +commits.label.files=Files +commits.label.segments=Segments (click rows for more details) +commits.label.segdetails=Segment details +# Logs +logs.label.see_also=See also: +# Help dialogs +help.fieldtype.TextField=A field that is indexed and tokenized, without term vectors.\n\n(Example Values)\n- Hello Lucene! +help.fieldtype.StringField=A field that is indexed but not tokenized: the entire String value is indexed as a single token.\n\n(Example Values)\n- Java +help.fieldtype.IntPoint=An indexed int field for fast range filters.\nIf you also need to store the value, you should add a separate StoredField instance.\nFinding all documents within an N-dimensional shape or range at search time is efficient. Multiple values for the same field in one document is allowed.\n\n(Example Values)\n- 1\n- 1,2,3\n\nFor multi dimensional data, comma-separated values are allowed. +help.fieldtype.LongPoint=An indexed long field for fast range filters.\nIf you also need to store the value, you should add a separate StoredField instance.\nFinding all documents within an N-dimensional shape or range at search time is efficient. Multiple values for the same field in one document is allowed.\n\n(Example Values)\n- 1\n- 1,2,3\n\nFor multi dimensional data, comma-separated values are allowed. +help.fieldtype.FloatPoint=An indexed float field for fast range filters.\nIf you also need to store the value, you should add a separate StoredField instance.\nFinding all documents within an N-dimensional shape or range at search time is efficient. Multiple values for the same field in one document is allowed.\n\n(Example Values)\n- 1.0\n- 42,3.14,2.718\n\nFor multi dimensional data, comma-separated values are allowed. +help.fieldtype.DoublePoint=An indexed double field for fast range filters.\nIf you also need to store the value, you should add a separate StoredField instance.\nFinding all documents within an N-dimensional shape or range at search time is efficient. Multiple values for the same field in one document is allowed.\n\n(Example Values)\n- 1.0\n- 42,3.14,2.718\n\nFor multi dimensional data, comma-separated values are allowed. +help.fieldtype.SortedDocValuesField=Field that stores a per-document BytesRef value, indexed for sorting.\nIf you also need to store the value, you should add a separate StoredField instance.\n\n(Example Values)\n- ID1234 +help.fieldtype.SortedSetDocValuesField=Field that stores a set of per-document BytesRef values, indexed for faceting,grouping,joining.\nIf you also need to store the value, you should add a separate StoredField instance.\n\n(Example Values)\n- red\n- blue +help.fieldtype.NumericDocValuesField=Field that stores a per-document long value for scoring, sorting or value retrieval.\nIf you also need to store the value, you should add a separate StoredField instance.\nDoubles or Floats will be encoded with org.apache.lucene.util.NumericUtils.\n\n(Example Values)\n- 42\n- 3.14 +help.fieldtype.SortedNumericDocValuesField=Field that stores a per-document long values for scoring, sorting or value retrieval.\nIf you also need to store the value, you should add a separate StoredField instance.\nDoubles or Floats will be encoded with org.apache.lucene.util.NumericUtils.\n\n(Example Values)\n- 42\n- 3.14 +help.fieldtype.StoredField=A field whose value is stored.\n\n(Example Values)\n- Hello Lucene! +help.fieldtype.Field=Expert: directly create a field for a document. Most users should use one of the sugar subclasses above. \ No newline at end of file diff --git a/lucene/luke/src/test/log4j2.xml b/lucene/luke/src/test/log4j2.xml new file mode 100644 index 000000000000..2c7c3fd7af15 --- /dev/null +++ b/lucene/luke/src/test/log4j2.xml @@ -0,0 +1,28 @@ + + + + + + + [%d{ISO8601}] %5p (%F:%L) - %m%n + + + + + + \ No newline at end of file diff --git a/lucene/luke/src/test/org/apache/lucene/luke/models/analysis/AnalysisImplTest.java b/lucene/luke/src/test/org/apache/lucene/luke/models/analysis/AnalysisImplTest.java new file mode 100644 index 000000000000..c56ac30f9391 --- /dev/null +++ b/lucene/luke/src/test/org/apache/lucene/luke/models/analysis/AnalysisImplTest.java @@ -0,0 +1,129 @@ +/* + * 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.models.analysis; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import com.google.common.collect.ImmutableMap; +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.custom.CustomAnalyzer; +import org.apache.lucene.luke.models.LukeException; +import org.junit.Test; + +public class AnalysisImplTest extends AnalysisTestBase { + + @Test + public void testGetPresetAnalyzerTypes() throws Exception { + AnalysisImpl analysis = new AnalysisImpl(); + Collection> analyerTypes = analysis.getPresetAnalyzerTypes(); + assertNotNull(analyerTypes); + for (Class clazz : analyerTypes) { + clazz.newInstance(); + } + } + + @Test + public void testGetAvailableCharFilters() { + AnalysisImpl analysis = new AnalysisImpl(); + Collection charFilters = analysis.getAvailableCharFilters(); + assertNotNull(charFilters); + } + + @Test + public void testGetAvailableTokenizers() { + AnalysisImpl analysis = new AnalysisImpl(); + Collection tokenizers = analysis.getAvailableTokenizers(); + assertNotNull(tokenizers); + } + + @Test + public void testGetAvailableTokenFilters() { + AnalysisImpl analysis = new AnalysisImpl(); + Collection tokenFilters = analysis.getAvailableTokenFilters(); + assertNotNull(tokenFilters); + } + + @Test + public void testAnalyze_preset() throws Exception { + AnalysisImpl analysis = new AnalysisImpl(); + String analyzerType = "org.apache.lucene.analysis.standard.StandardAnalyzer"; + Analyzer analyzer = analysis.createAnalyzerFromClassName(analyzerType); + assertEquals(analyzerType, analyzer.getClass().getName()); + + String text = "It is a truth universally acknowledged, that a single man in possession of a good fortune, must be in want of a wife."; + List tokens = analysis.analyze(text); + assertNotNull(tokens); + } + + @Test + public void testAnalyze_custom() throws Exception { + AnalysisImpl analysis = new AnalysisImpl(); + CustomAnalyzerConfig.Builder builder = new CustomAnalyzerConfig.Builder( + "keyword", + ImmutableMap.of("maxTokenLen", "128")) + .addTokenFilterConfig("lowercase", Collections.emptyMap()); + CustomAnalyzer analyzer = (CustomAnalyzer) analysis.buildCustomAnalyzer(builder.build()); + assertEquals("org.apache.lucene.analysis.custom.CustomAnalyzer", analyzer.getClass().getName()); + assertEquals("org.apache.lucene.analysis.core.KeywordTokenizerFactory", analyzer.getTokenizerFactory().getClass().getName()); + assertEquals("org.apache.lucene.analysis.core.LowerCaseFilterFactory", analyzer.getTokenFilterFactories().get(0).getClass().getName()); + + String text = "Apache Lucene"; + List tokens = analysis.analyze(text); + assertNotNull(tokens); + } + + @Test + public void testAnalyzer_custom_with_confdir() throws Exception { + Path confDir = createTempDir("conf"); + Path stopFile = Files.createFile(Paths.get(confDir.toString(), "stop.txt")); + Files.write(stopFile, "of\nthe\nby\nfor\n".getBytes(StandardCharsets.UTF_8)); + + AnalysisImpl analysis = new AnalysisImpl(); + CustomAnalyzerConfig.Builder builder = new CustomAnalyzerConfig.Builder( + "whitespace", + ImmutableMap.of("maxTokenLen", "128")) + .configDir(confDir.toString()) + .addTokenFilterConfig("lowercase", Collections.emptyMap()) + .addTokenFilterConfig("stop", + ImmutableMap.of("ignoreCase", "true", "words", "stop.txt", "format", "wordset")); + CustomAnalyzer analyzer = (CustomAnalyzer) analysis.buildCustomAnalyzer(builder.build()); + assertEquals("org.apache.lucene.analysis.custom.CustomAnalyzer", analyzer.getClass().getName()); + assertEquals("org.apache.lucene.analysis.core.WhitespaceTokenizerFactory", analyzer.getTokenizerFactory().getClass().getName()); + assertEquals("org.apache.lucene.analysis.core.LowerCaseFilterFactory", analyzer.getTokenFilterFactories().get(0).getClass().getName()); + assertEquals("org.apache.lucene.analysis.core.StopFilterFactory", analyzer.getTokenFilterFactories().get(1).getClass().getName()); + + String text = "Government of the People, by the People, for the People"; + List tokens = analysis.analyze(text); + assertNotNull(tokens); + } + + @Test(expected = LukeException.class) + public void testAnalyze_not_set() { + AnalysisImpl analysis = new AnalysisImpl(); + String text = "This test must fail."; + analysis.analyze(text); + } + + +} diff --git a/lucene/luke/src/test/org/apache/lucene/luke/models/analysis/AnalysisTestBase.java b/lucene/luke/src/test/org/apache/lucene/luke/models/analysis/AnalysisTestBase.java new file mode 100644 index 000000000000..6ded15d25ba8 --- /dev/null +++ b/lucene/luke/src/test/org/apache/lucene/luke/models/analysis/AnalysisTestBase.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.models.analysis; + +import org.apache.lucene.util.LuceneTestCase; + +public class AnalysisTestBase extends LuceneTestCase { +} diff --git a/lucene/luke/src/test/org/apache/lucene/luke/models/commits/CommitsImplTest.java b/lucene/luke/src/test/org/apache/lucene/luke/models/commits/CommitsImplTest.java new file mode 100644 index 000000000000..a3da98b251c0 --- /dev/null +++ b/lucene/luke/src/test/org/apache/lucene/luke/models/commits/CommitsImplTest.java @@ -0,0 +1,214 @@ +/* + * 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.models.commits; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.apache.lucene.analysis.MockAnalyzer; +import org.apache.lucene.codecs.Codec; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.NoDeletionPolicy; +import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.store.Directory; +import org.apache.lucene.util.LuceneTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +// See: https://github.com/DmitryKey/luke/issues/111 +@LuceneTestCase.SuppressCodecs({ + "CheapBastard", + "DummyCompressingStoredFields", "HighCompressionCompressingStoredFields", "FastCompressingStoredFields", "FastDecompressionCompressingStoredFields" +}) +public class CommitsImplTest extends LuceneTestCase { + + private DirectoryReader reader; + + private Directory dir; + + private Path indexDir; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + indexDir = createIndex(); + dir = newFSDirectory(indexDir); + reader = DirectoryReader.open(dir); + } + + private Path createIndex() throws IOException { + Path indexDir = createTempDir(); + + Directory dir = newFSDirectory(indexDir); + + IndexWriterConfig config = new IndexWriterConfig(new MockAnalyzer(random())); + config.setIndexDeletionPolicy(NoDeletionPolicy.INSTANCE); + RandomIndexWriter writer = new RandomIndexWriter(random(), dir, config); + + Document doc1 = new Document(); + doc1.add(newStringField("f1", "1", Field.Store.NO)); + writer.addDocument(doc1); + + writer.commit(); + + Document doc2 = new Document(); + doc2.add(newStringField("f1", "2", Field.Store.NO)); + writer.addDocument(doc2); + + Document doc3 = new Document(); + doc3.add(newStringField("f1", "3", Field.Store.NO)); + writer.addDocument(doc3); + + writer.commit(); + + writer.close(); + dir.close(); + + return indexDir; + } + + @Override + @After + public void tearDown() throws Exception { + super.tearDown(); + reader.close(); + dir.close(); + } + + @Test + public void testListCommits() { + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); + List commitList = commits.listCommits(); + assertEquals(2, commitList.size()); + assertEquals(2, commitList.get(0).getGeneration()); + assertEquals(1, commitList.get(1).getGeneration()); + } + + @Test + public void testGetCommit() { + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); + Optional commit = commits.getCommit(1); + assertTrue(commit.isPresent()); + assertEquals(1, commit.get().getGeneration()); + } + + @Test + public void testGetCommit_generation_notfound() { + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); + assertFalse(commits.getCommit(10).isPresent()); + } + + @Test + public void testGetFiles() { + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); + List files = commits.getFiles(1); + assertTrue(files.size() > 0); + assertTrue(files.stream().anyMatch(file -> file.getFileName().equals("segments_1"))); + } + + @Test + public void testGetFiles_generation_notfound() { + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); + assertTrue(commits.getFiles(10).isEmpty()); + } + + @Test + public void testGetSegments() { + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); + List segments = commits.getSegments(1); + assertTrue(segments.size() > 0); + } + + @Test + public void testGetSegments_generation_notfound() { + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); + assertTrue(commits.getSegments(10).isEmpty()); + } + + @Test + public void testGetSegmentAttributes() { + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); + Map attributes = commits.getSegmentAttributes(1, "_0"); + assertTrue(attributes.size() > 0); + } + + @Test + public void testGetSegmentAttributes_generation_notfound() { + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); + Map attributes = commits.getSegmentAttributes(3, "_0"); + assertTrue(attributes.isEmpty()); + } + + @Test + public void testGetSegmentAttributes_invalid_name() { + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); + Map attributes = commits.getSegmentAttributes(1, "xxx"); + assertTrue(attributes.isEmpty()); + } + + @Test + public void testGetSegmentDiagnostics() { + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); + Map diagnostics = commits.getSegmentDiagnostics(1, "_0"); + assertTrue(diagnostics.size() > 0); + } + + @Test + public void testGetSegmentDiagnostics_generation_notfound() { + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); + assertTrue(commits.getSegmentDiagnostics(10, "_0").isEmpty()); + } + + + @Test + public void testGetSegmentDiagnostics_invalid_name() { + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); + Map diagnostics = commits.getSegmentDiagnostics(1, "xxx"); + assertTrue(diagnostics.isEmpty()); + } + + @Test + public void testSegmentCodec() { + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); + Optional codec = commits.getSegmentCodec(1, "_0"); + assertTrue(codec.isPresent()); + } + + @Test + public void testSegmentCodec_generation_notfound() { + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); + Optional codec = commits.getSegmentCodec(10, "_0"); + assertFalse(codec.isPresent()); + } + + @Test + public void testSegmentCodec_invalid_name() { + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); + Optional codec = commits.getSegmentCodec(1, "xxx"); + assertFalse(codec.isPresent()); + + } +} diff --git a/lucene/luke/src/test/org/apache/lucene/luke/models/documents/DocValuesAdapterTest.java b/lucene/luke/src/test/org/apache/lucene/luke/models/documents/DocValuesAdapterTest.java new file mode 100644 index 000000000000..e6349bf1e9db --- /dev/null +++ b/lucene/luke/src/test/org/apache/lucene/luke/models/documents/DocValuesAdapterTest.java @@ -0,0 +1,114 @@ +/* + * 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.models.documents; + +import java.io.IOException; +import java.util.Collections; + +import org.apache.lucene.analysis.MockAnalyzer; +import org.apache.lucene.document.BinaryDocValuesField; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +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.index.DocValuesType; +import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.store.Directory; +import org.apache.lucene.util.BytesRef; +import org.junit.Test; + +public class DocValuesAdapterTest extends DocumentsTestBase { + + @Override + protected void createIndex() throws IOException { + indexDir = createTempDir("testIndex"); + + Directory dir = newFSDirectory(indexDir); + RandomIndexWriter writer = new RandomIndexWriter(random(), dir, new MockAnalyzer(random())); + + Document doc = new Document(); + doc.add(new BinaryDocValuesField("dv_binary", new BytesRef("lucene"))); + doc.add(new SortedDocValuesField("dv_sorted", new BytesRef("abc"))); + doc.add(new SortedSetDocValuesField("dv_sortedset", new BytesRef("python"))); + doc.add(new SortedSetDocValuesField("dv_sortedset", new BytesRef("java"))); + doc.add(new NumericDocValuesField("dv_numeric", 42L)); + doc.add(new SortedNumericDocValuesField("dv_sortednumeric", 22L)); + doc.add(new SortedNumericDocValuesField("dv_sortednumeric", 11L)); + doc.add(newStringField("no_dv", "aaa", Field.Store.NO)); + writer.addDocument(doc); + + writer.commit(); + writer.close(); + dir.close(); + } + + @Test + public void testGetDocValues_binary() throws Exception { + DocValuesAdapter adapterImpl = new DocValuesAdapter(reader); + DocValues values = adapterImpl.getDocValues(0, "dv_binary").orElseThrow(IllegalStateException::new); + assertEquals(DocValuesType.BINARY, values.getDvType()); + assertEquals(new BytesRef("lucene"), values.getValues().get(0)); + assertEquals(Collections.emptyList(), values.getNumericValues()); + } + + @Test + public void testGetDocValues_sorted() throws Exception { + DocValuesAdapter adapterImpl = new DocValuesAdapter(reader); + DocValues values = adapterImpl.getDocValues(0, "dv_sorted").orElseThrow(IllegalStateException::new); + assertEquals(DocValuesType.SORTED, values.getDvType()); + assertEquals(new BytesRef("abc"), values.getValues().get(0)); + assertEquals(Collections.emptyList(), values.getNumericValues()); + } + + @Test + public void testGetDocValues_sorted_set() throws Exception { + DocValuesAdapter adapterImpl = new DocValuesAdapter(reader); + DocValues values = adapterImpl.getDocValues(0, "dv_sortedset").orElseThrow(IllegalStateException::new); + assertEquals(DocValuesType.SORTED_SET, values.getDvType()); + assertEquals(new BytesRef("java"), values.getValues().get(0)); + assertEquals(new BytesRef("python"), values.getValues().get(1)); + assertEquals(Collections.emptyList(), values.getNumericValues()); + } + + @Test + public void testGetDocValues_numeric() throws Exception { + DocValuesAdapter adapterImpl = new DocValuesAdapter(reader); + DocValues values = adapterImpl.getDocValues(0, "dv_numeric").orElseThrow(IllegalStateException::new); + assertEquals(DocValuesType.NUMERIC, values.getDvType()); + assertEquals(Collections.emptyList(), values.getValues()); + assertEquals(42L, values.getNumericValues().get(0).longValue()); + } + + @Test + public void testGetDocValues_sorted_numeric() throws Exception { + DocValuesAdapter adapterImpl = new DocValuesAdapter(reader); + DocValues values = adapterImpl.getDocValues(0, "dv_sortednumeric").orElseThrow(IllegalStateException::new); + assertEquals(DocValuesType.SORTED_NUMERIC, values.getDvType()); + assertEquals(Collections.emptyList(), values.getValues()); + assertEquals(11L, values.getNumericValues().get(0).longValue()); + assertEquals(22L, values.getNumericValues().get(1).longValue()); + } + + @Test + public void testGetDocValues_notAvailable() throws Exception { + DocValuesAdapter adapterImpl = new DocValuesAdapter(reader); + assertFalse(adapterImpl.getDocValues(0, "no_dv").isPresent()); + } +} diff --git a/lucene/luke/src/test/org/apache/lucene/luke/models/documents/DocumentsImplTest.java b/lucene/luke/src/test/org/apache/lucene/luke/models/documents/DocumentsImplTest.java new file mode 100644 index 000000000000..cebb4d7efee0 --- /dev/null +++ b/lucene/luke/src/test/org/apache/lucene/luke/models/documents/DocumentsImplTest.java @@ -0,0 +1,248 @@ +/* + * 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.models.documents; + +import java.util.List; + +import org.apache.lucene.index.DocValuesType; +import org.apache.lucene.index.IndexOptions; +import org.apache.lucene.index.Term; +import org.apache.lucene.luke.models.util.IndexUtils; +import org.apache.lucene.store.AlreadyClosedException; +import org.apache.lucene.util.LuceneTestCase; +import org.apache.lucene.util.NumericUtils; +import org.junit.Test; + +// See: https://github.com/DmitryKey/luke/issues/111 +@LuceneTestCase.SuppressCodecs({ + "CheapBastard", + "DummyCompressingStoredFields", "HighCompressionCompressingStoredFields", "FastCompressingStoredFields", "FastDecompressionCompressingStoredFields" +}) +public class DocumentsImplTest extends DocumentsTestBase { + + @Test + public void testGetMaxDoc() { + DocumentsImpl documents = new DocumentsImpl(reader); + assertEquals(5, documents.getMaxDoc()); + } + + @Test + public void testIsLive() { + DocumentsImpl documents = new DocumentsImpl(reader); + assertTrue(documents.isLive(0)); + } + + @Test + public void testGetDocumentFields() { + DocumentsImpl documents = new DocumentsImpl(reader); + List fields = documents.getDocumentFields(0); + assertEquals(5, fields.size()); + + DocumentField f1 = fields.get(0); + assertEquals("title", f1.getName()); + assertEquals(IndexOptions.DOCS_AND_FREQS, f1.getIdxOptions()); + assertFalse(f1.hasTermVectors()); + assertFalse(f1.hasPayloads()); + assertFalse(f1.hasNorms()); + assertEquals(0, f1.getNorm()); + assertTrue(f1.isStored()); + assertEquals("Pride and Prejudice", f1.getStringValue()); + assertNull(f1.getBinaryValue()); + assertNull(f1.getNumericValue()); + assertEquals(DocValuesType.NONE, f1.getDvType()); + assertEquals(0, f1.getPointDimensionCount()); + assertEquals(0, f1.getPointNumBytes()); + + DocumentField f2 = fields.get(1); + assertEquals("author", f2.getName()); + assertEquals(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS, f2.getIdxOptions()); + assertFalse(f2.hasTermVectors()); + assertFalse(f2.hasPayloads()); + assertTrue(f2.hasNorms()); + assertTrue(f2.getNorm() > 0); + assertTrue(f2.isStored()); + assertEquals("Jane Austen", f2.getStringValue()); + assertNull(f2.getBinaryValue()); + assertNull(f2.getNumericValue()); + assertEquals(DocValuesType.NONE, f2.getDvType()); + assertEquals(0, f2.getPointDimensionCount()); + assertEquals(0, f2.getPointNumBytes()); + + DocumentField f3 = fields.get(2); + assertEquals("text", f3.getName()); + assertEquals(IndexOptions.DOCS_AND_FREQS, f3.getIdxOptions()); + assertTrue(f3.hasTermVectors()); + assertFalse(f3.hasPayloads()); + assertTrue(f3.hasNorms()); + assertTrue(f3.getNorm() > 0); + assertFalse(f3.isStored()); + assertNull(f3.getStringValue()); + assertNull(f3.getBinaryValue()); + assertNull(f3.getNumericValue()); + assertEquals(DocValuesType.NONE, f3.getDvType()); + assertEquals(0, f3.getPointDimensionCount()); + assertEquals(0, f3.getPointNumBytes()); + + DocumentField f4 = fields.get(3); + assertEquals("subject", f4.getName()); + assertEquals(IndexOptions.NONE, f4.getIdxOptions()); + assertFalse(f4.hasTermVectors()); + assertFalse(f4.hasPayloads()); + assertFalse(f4.hasNorms()); + assertEquals(0, f4.getNorm()); + assertFalse(f4.isStored()); + assertNull(f4.getStringValue()); + assertNull(f4.getBinaryValue()); + assertNull(f4.getNumericValue()); + assertEquals(DocValuesType.SORTED_SET, f4.getDvType()); + assertEquals(0, f4.getPointDimensionCount()); + assertEquals(0, f4.getPointNumBytes()); + + DocumentField f5 = fields.get(4); + assertEquals("downloads", f5.getName()); + assertEquals(IndexOptions.NONE, f5.getIdxOptions()); + assertFalse(f5.hasTermVectors()); + assertFalse(f5.hasPayloads()); + assertFalse(f5.hasNorms()); + assertEquals(0, f5.getNorm()); + assertTrue(f5.isStored()); + assertNull(f5.getStringValue()); + assertEquals(28533, NumericUtils.sortableBytesToInt(f5.getBinaryValue().bytes, 0)); + assertNull(f5.getNumericValue()); + } + + @Test + public void testFirstTerm() { + DocumentsImpl documents = new DocumentsImpl(reader); + Term term = documents.firstTerm("title").orElseThrow(IllegalStateException::new); + assertEquals("title", documents.getCurrentField()); + assertEquals("a", term.text()); + } + + @Test + public void testFirstTerm_notAvailable() { + DocumentsImpl documents = new DocumentsImpl(reader); + assertFalse(documents.firstTerm("subject").isPresent()); + assertNull(documents.getCurrentField()); + } + + @Test + public void testNextTerm() { + DocumentsImpl documents = new DocumentsImpl(reader); + documents.firstTerm("title").orElseThrow(IllegalStateException::new); + Term term = documents.nextTerm().orElseThrow(IllegalStateException::new); + assertEquals("adventures", term.text()); + + while (documents.nextTerm().isPresent()) { + Integer freq = documents.getDocFreq().orElseThrow(IllegalStateException::new); + } + } + + @Test + public void testNextTerm_unPositioned() { + DocumentsImpl documents = new DocumentsImpl(reader); + assertFalse(documents.nextTerm().isPresent()); + } + + @Test + public void testSeekTerm() { + DocumentsImpl documents = new DocumentsImpl(reader); + documents.firstTerm("title").orElseThrow(IllegalStateException::new); + Term term = documents.seekTerm("pri").orElseThrow(IllegalStateException::new); + assertEquals("pride", term.text()); + + assertFalse(documents.seekTerm("x").isPresent()); + } + + @Test + public void testSeekTerm_unPositioned() { + DocumentsImpl documents = new DocumentsImpl(reader); + assertFalse(documents.seekTerm("a").isPresent()); + } + + @Test + public void testFirstTermDoc() { + DocumentsImpl documents = new DocumentsImpl(reader); + documents.firstTerm("title").orElseThrow(IllegalStateException::new); + Term term = documents.seekTerm("adv").orElseThrow(IllegalStateException::new); + assertEquals("adventures", term.text()); + int docid = documents.firstTermDoc().orElseThrow(IllegalStateException::new); + assertEquals(1, docid); + } + + @Test + public void testFirstTermDoc_unPositioned() { + DocumentsImpl documents = new DocumentsImpl(reader); + assertFalse(documents.firstTermDoc().isPresent()); + } + + @Test + public void testNextTermDoc() { + DocumentsImpl documents = new DocumentsImpl(reader); + Term term = documents.firstTerm("title").orElseThrow(IllegalStateException::new); + term = documents.seekTerm("adv").orElseThrow(IllegalStateException::new); + assertEquals("adventures", term.text()); + int docid = documents.firstTermDoc().orElseThrow(IllegalStateException::new); + docid = documents.nextTermDoc().orElseThrow(IllegalStateException::new); + assertEquals(4, docid); + + assertFalse(documents.nextTermDoc().isPresent()); + } + + @Test + public void testNextTermDoc_unPositioned() { + DocumentsImpl documents = new DocumentsImpl(reader); + Term term = documents.firstTerm("title").orElseThrow(IllegalStateException::new); + assertFalse(documents.nextTermDoc().isPresent()); + } + + @Test + public void testTermPositions() { + DocumentsImpl documents = new DocumentsImpl(reader); + Term term = documents.firstTerm("author").orElseThrow(IllegalStateException::new); + term = documents.seekTerm("carroll").orElseThrow(IllegalStateException::new); + int docid = documents.firstTermDoc().orElseThrow(IllegalStateException::new); + List postings = documents.getTermPositions(); + assertEquals(1, postings.size()); + assertEquals(1, postings.get(0).getPosition()); + assertEquals(6, postings.get(0).getStartOffset()); + assertEquals(13, postings.get(0).getEndOffset()); + } + + @Test + public void testTermPositions_unPositioned() { + DocumentsImpl documents = new DocumentsImpl(reader); + Term term = documents.firstTerm("author").orElseThrow(IllegalStateException::new); + assertEquals(0, documents.getTermPositions().size()); + } + + @Test + public void testTermPositions_noPositions() { + DocumentsImpl documents = new DocumentsImpl(reader); + Term term = documents.firstTerm("title").orElseThrow(IllegalStateException::new); + int docid = documents.firstTermDoc().orElseThrow(IllegalStateException::new); + assertEquals(0, documents.getTermPositions().size()); + } + + @Test(expected = AlreadyClosedException.class) + public void testClose() throws Exception { + DocumentsImpl documents = new DocumentsImpl(reader); + reader.close(); + IndexUtils.getFieldNames(reader); + } +} diff --git a/lucene/luke/src/test/org/apache/lucene/luke/models/documents/DocumentsTestBase.java b/lucene/luke/src/test/org/apache/lucene/luke/models/documents/DocumentsTestBase.java new file mode 100644 index 000000000000..58519fa90ed1 --- /dev/null +++ b/lucene/luke/src/test/org/apache/lucene/luke/models/documents/DocumentsTestBase.java @@ -0,0 +1,152 @@ +/* + * 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.models.documents; + +import java.io.IOException; +import java.nio.file.Path; + +import org.apache.lucene.analysis.standard.StandardAnalyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.FieldType; +import org.apache.lucene.document.SortedSetDocValuesField; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexOptions; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.store.Directory; +import org.apache.lucene.util.BytesRef; +import org.apache.lucene.util.LuceneTestCase; +import org.apache.lucene.util.NumericUtils; +import org.junit.After; +import org.junit.Before; + +public abstract class DocumentsTestBase extends LuceneTestCase { + protected IndexReader reader; + protected Directory dir; + protected Path indexDir; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + createIndex(); + dir = newFSDirectory(indexDir); + reader = DirectoryReader.open(dir); + } + + protected void createIndex() throws IOException { + indexDir = createTempDir(); + + Directory dir = newFSDirectory(indexDir); + RandomIndexWriter writer = new RandomIndexWriter(random(), dir, new StandardAnalyzer()); + + FieldType titleType = new FieldType(); + titleType.setIndexOptions(IndexOptions.DOCS_AND_FREQS); + titleType.setStored(true); + titleType.setTokenized(true); + titleType.setOmitNorms(true); + + FieldType authorType = new FieldType(); + authorType.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS); + authorType.setStored(true); + authorType.setTokenized(true); + authorType.setOmitNorms(false); + + FieldType textType = new FieldType(); + textType.setIndexOptions(IndexOptions.DOCS_AND_FREQS); + textType.setStored(false); + textType.setTokenized(true); + textType.setStoreTermVectors(true); + textType.setOmitNorms(false); + + FieldType downloadsType = new FieldType(); + downloadsType.setDimensions(1, Integer.BYTES); + downloadsType.setStored(true); + + Document doc1 = new Document(); + doc1.add(newField("title", "Pride and Prejudice", titleType)); + doc1.add(newField("author", "Jane Austen", authorType)); + doc1.add(newField("text", + "It is a truth universally acknowledged, that a single man in possession of a good fortune, must be in want of a wife.", + textType)); + doc1.add(new SortedSetDocValuesField("subject", new BytesRef("Fiction"))); + doc1.add(new SortedSetDocValuesField("subject", new BytesRef("Love stories"))); + doc1.add(new Field("downloads", packInt(28533), downloadsType)); + writer.addDocument(doc1); + + Document doc2 = new Document(); + doc2.add(newField("title", "Alice's Adventures in Wonderland", titleType)); + doc2.add(newField("author", "Lewis Carroll", authorType)); + doc2.add(newField("text", "Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, ‘and what is the use of a book,’ thought Alice ‘without pictures or conversations?’", + textType)); + doc2.add(new SortedSetDocValuesField("subject", new BytesRef("Fantasy literature"))); + doc2.add(new Field("downloads", packInt(18712), downloadsType)); + writer.addDocument(doc2); + + Document doc3 = new Document(); + doc3.add(newField("title", "Frankenstein; Or, The Modern Prometheus", titleType)); + doc3.add(newField("author", "Mary Wollstonecraft Shelley", authorType)); + doc3.add(newField("text", "You will rejoice to hear that no disaster has accompanied the commencement of an enterprise which you have regarded with such evil forebodings. I arrived here yesterday, and my first task is to assure my dear sister of my welfare and increasing confidence in the success of my undertaking.", + textType)); + doc3.add(new SortedSetDocValuesField("subject", new BytesRef("Science fiction"))); + doc3.add(new SortedSetDocValuesField("subject", new BytesRef("Horror tales"))); + doc3.add(new SortedSetDocValuesField("subject", new BytesRef("Monsters"))); + doc3.add(new Field("downloads", packInt(14737), downloadsType)); + writer.addDocument(doc3); + + Document doc4 = new Document(); + doc4.add(newField("title", "A Doll's House : a play", titleType)); + doc4.add(newField("author", "Henrik Ibsen", authorType)); + doc4.add(newField("text", "", + textType)); + doc4.add(new SortedSetDocValuesField("subject", new BytesRef("Drama"))); + doc4.add(new Field("downloads", packInt(14629), downloadsType)); + writer.addDocument(doc4); + + Document doc5 = new Document(); + doc5.add(newField("title", "The Adventures of Sherlock Holmes", titleType)); + doc5.add(newField("author", "Arthur Conan Doyle", authorType)); + doc5.add(newField("text", "To Sherlock Holmes she is always the woman. I have seldom heard him mention her under any other name. In his eyes she eclipses and predominates the whole of her sex.", + textType)); + doc5.add(new SortedSetDocValuesField("subject", new BytesRef("Fiction"))); + doc5.add(new SortedSetDocValuesField("subject", new BytesRef("Detective and mystery stories"))); + doc5.add(new Field("downloads", packInt(12828), downloadsType)); + writer.addDocument(doc5); + + writer.commit(); + + writer.close(); + dir.close(); + } + + private BytesRef packInt(int value) { + byte[] dest = new byte[Integer.BYTES]; + NumericUtils.intToSortableBytes(value, dest, 0); + return new BytesRef(dest); + } + + @Override + @After + public void tearDown() throws Exception { + super.tearDown(); + reader.close(); + dir.close(); + } + +} diff --git a/lucene/luke/src/test/org/apache/lucene/luke/models/documents/TermVectorsAdapterTest.java b/lucene/luke/src/test/org/apache/lucene/luke/models/documents/TermVectorsAdapterTest.java new file mode 100644 index 000000000000..49e3689b8b33 --- /dev/null +++ b/lucene/luke/src/test/org/apache/lucene/luke/models/documents/TermVectorsAdapterTest.java @@ -0,0 +1,133 @@ +/* + * 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.models.documents; + +import java.io.IOException; +import java.util.List; + +import org.apache.lucene.analysis.standard.StandardAnalyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.FieldType; +import org.apache.lucene.index.IndexOptions; +import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.store.Directory; +import org.apache.lucene.util.LuceneTestCase; +import org.junit.Test; + +@LuceneTestCase.SuppressCodecs({ + "CheapBastard", + "DummyCompressingStoredFields", "HighCompressionCompressingStoredFields", "FastCompressingStoredFields", "FastDecompressionCompressingStoredFields" +}) +public class TermVectorsAdapterTest extends DocumentsTestBase { + + @Override + protected void createIndex() throws IOException { + indexDir = createTempDir("testIndex"); + + Directory dir = newFSDirectory(indexDir); + RandomIndexWriter writer = new RandomIndexWriter(random(), dir, new StandardAnalyzer()); + + FieldType textType = new FieldType(); + textType.setIndexOptions(IndexOptions.DOCS_AND_FREQS); + textType.setTokenized(true); + textType.setStoreTermVectors(true); + + FieldType textType_pos = new FieldType(); + textType_pos.setIndexOptions(IndexOptions.DOCS_AND_FREQS); + textType_pos.setTokenized(true); + textType_pos.setStoreTermVectors(true); + textType_pos.setStoreTermVectorPositions(true); + + FieldType textType_pos_offset = new FieldType(); + textType_pos_offset.setIndexOptions(IndexOptions.DOCS_AND_FREQS); + textType_pos_offset.setTokenized(true); + textType_pos_offset.setStoreTermVectors(true); + textType_pos_offset.setStoreTermVectorPositions(true); + textType_pos_offset.setStoreTermVectorOffsets(true); + + String text = "It is a truth universally acknowledged, that a single man in possession of a good fortune, must be in want of a wife."; + Document doc = new Document(); + doc.add(newField("text1", text, textType)); + doc.add(newField("text2", text, textType_pos)); + doc.add(newField("text3", text, textType_pos_offset)); + writer.addDocument(doc); + + writer.commit(); + writer.close(); + dir.close(); + } + + @Test + public void testGetTermVector() throws Exception { + TermVectorsAdapter adapterImpl = new TermVectorsAdapter(reader); + List tvEntries = adapterImpl.getTermVector(0, "text1"); + + assertEquals(18, tvEntries.size()); + + assertEquals("a", tvEntries.get(0).getTermText()); + assertEquals(4, tvEntries.get(0).getFreq()); + assertEquals(0, tvEntries.get(0).getPositions().size()); + + assertEquals("acknowledged", tvEntries.get(1).getTermText()); + assertEquals(1, tvEntries.get(1).getFreq()); + assertEquals(0, tvEntries.get(1).getPositions().size()); + + assertEquals("be", tvEntries.get(2).getTermText()); + assertEquals(1, tvEntries.get(2).getFreq()); + + assertEquals("fortune", tvEntries.get(3).getTermText()); + assertEquals(1, tvEntries.get(3).getFreq()); + + assertEquals("good", tvEntries.get(4).getTermText()); + assertEquals(1, tvEntries.get(4).getFreq()); + } + + @Test + public void testGetTermVector_with_positions() throws Exception { + TermVectorsAdapter adapterImpl = new TermVectorsAdapter(reader); + List tvEntries = adapterImpl.getTermVector(0, "text2"); + + assertEquals(18, tvEntries.size()); + + assertEquals("a", tvEntries.get(0).getTermText()); + assertEquals(4, tvEntries.get(0).getFreq()); + assertEquals(2, tvEntries.get(0).getPositions().get(0).getPosition()); + assertFalse(tvEntries.get(0).getPositions().get(0).getStartOffset().isPresent()); + assertFalse(tvEntries.get(0).getPositions().get(0).getEndOffset().isPresent()); + } + + @Test + public void testGetTermVector_with_positions_offsets() throws Exception { + TermVectorsAdapter adapterImpl = new TermVectorsAdapter(reader); + List tvEntries = adapterImpl.getTermVector(0, "text3"); + + assertEquals(18, tvEntries.size()); + + assertEquals("acknowledged", tvEntries.get(1).getTermText()); + assertEquals(1, tvEntries.get(1).getFreq()); + assertEquals(5, tvEntries.get(1).getPositions().get(0).getPosition()); + assertEquals(26, tvEntries.get(1).getPositions().get(0).getStartOffset().orElse(-1)); + assertEquals(38, tvEntries.get(1).getPositions().get(0).getEndOffset().orElse(-1)); + } + + @Test + public void testGetTermVectors_notAvailable() throws Exception { + TermVectorsAdapter adapterImpl = new TermVectorsAdapter(reader); + assertEquals(0, adapterImpl.getTermVector(0, "title").size()); + } +} diff --git a/lucene/luke/src/test/org/apache/lucene/luke/models/overview/OverviewImplTest.java b/lucene/luke/src/test/org/apache/lucene/luke/models/overview/OverviewImplTest.java new file mode 100644 index 000000000000..423d12772ffe --- /dev/null +++ b/lucene/luke/src/test/org/apache/lucene/luke/models/overview/OverviewImplTest.java @@ -0,0 +1,140 @@ +/* + * 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.models.overview; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import org.apache.lucene.store.AlreadyClosedException; +import org.junit.Test; + +public class OverviewImplTest extends OverviewTestBase { + + @Test + public void testGetIndexPath() { + OverviewImpl overview = new OverviewImpl(reader, indexDir.toString()); + assertEquals(indexDir.toString(), overview.getIndexPath()); + } + + @Test + public void testGetNumFields() { + OverviewImpl overview = new OverviewImpl(reader, indexDir.toString()); + assertEquals(2, (long) overview.getNumFields()); + } + + @Test + public void testGetFieldNames() { + OverviewImpl overview = new OverviewImpl(reader, indexDir.toString()); + assertEquals( + new HashSet<>(Arrays.asList("f1", "f2")), + new HashSet<>(overview.getFieldNames())); + } + + @Test + public void testGetNumDocuments() { + OverviewImpl overview = new OverviewImpl(reader, indexDir.toString()); + assertEquals(3, (long) overview.getNumDocuments()); + } + + @Test + public void testGetNumTerms() { + OverviewImpl overview = new OverviewImpl(reader, indexDir.toString()); + assertEquals(9, overview.getNumTerms()); + } + + @Test + public void testHasDeletions() { + OverviewImpl overview = new OverviewImpl(reader, indexDir.toString()); + assertFalse(overview.hasDeletions()); + } + + @Test + public void testGetNumDeletedDocs() { + OverviewImpl overview = new OverviewImpl(reader, indexDir.toString()); + assertEquals(0, (long) overview.getNumDeletedDocs()); + } + + @Test + public void testIsOptimized() { + OverviewImpl overview = new OverviewImpl(reader, indexDir.toString()); + assertTrue(overview.isOptimized().orElse(false)); + } + + @Test + public void testGetIndexVersion() { + OverviewImpl overview = new OverviewImpl(reader, indexDir.toString()); + assertTrue(overview.getIndexVersion().orElseThrow(IllegalStateException::new) > 0); + } + + @Test + public void testGetIndexFormat() { + OverviewImpl overview = new OverviewImpl(reader, indexDir.toString()); + assertEquals("Lucene 7.4 or later", overview.getIndexFormat().get()); + } + + @Test + public void testGetDirImpl() { + OverviewImpl overview = new OverviewImpl(reader, indexDir.toString()); + assertEquals(dir.getClass().getName(), overview.getDirImpl().get()); + } + + @Test + public void testGetCommitDescription() { + OverviewImpl overview = new OverviewImpl(reader, indexDir.toString()); + assertTrue(overview.getCommitDescription().isPresent()); + } + + @Test + public void testGetCommitUserData() { + OverviewImpl overview = new OverviewImpl(reader, indexDir.toString()); + assertTrue(overview.getCommitUserData().isPresent()); + } + + @Test + public void testGetSortedTermCounts() { + OverviewImpl overview = new OverviewImpl(reader, indexDir.toString()); + Map countsMap = overview.getSortedTermCounts(TermCountsOrder.COUNT_DESC); + assertEquals(Arrays.asList("f2", "f1"), new ArrayList<>(countsMap.keySet())); + } + + @Test + public void testGetTopTerms() { + OverviewImpl overview = new OverviewImpl(reader, indexDir.toString()); + List result = overview.getTopTerms("f2", 2); + assertEquals("a", result.get(0).getDecodedTermText()); + assertEquals(3, result.get(0).getDocFreq()); + assertEquals("f2", result.get(0).getField()); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetTopTerms_illegal_numterms() { + OverviewImpl overview = new OverviewImpl(reader, indexDir.toString()); + overview.getTopTerms("f2", -1); + } + + @Test(expected = AlreadyClosedException.class) + public void testClose() throws Exception { + OverviewImpl overview = new OverviewImpl(reader, indexDir.toString()); + reader.close(); + overview.getNumFields(); + } + +} diff --git a/lucene/luke/src/test/org/apache/lucene/luke/models/overview/OverviewTestBase.java b/lucene/luke/src/test/org/apache/lucene/luke/models/overview/OverviewTestBase.java new file mode 100644 index 000000000000..11f46bccb508 --- /dev/null +++ b/lucene/luke/src/test/org/apache/lucene/luke/models/overview/OverviewTestBase.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.models.overview; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; +import org.apache.lucene.analysis.MockAnalyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.TextField; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.store.Directory; +import org.apache.lucene.util.LuceneTestCase; +import org.junit.After; +import org.junit.Before; + +public abstract class OverviewTestBase extends LuceneTestCase { + + IndexReader reader; + + Directory dir; + + Path indexDir; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + indexDir = createIndex(); + dir = newFSDirectory(indexDir); + reader = DirectoryReader.open(dir); + } + + private Path createIndex() throws IOException { + Path indexDir = createTempDir(); + + Directory dir = newFSDirectory(indexDir); + RandomIndexWriter writer = new RandomIndexWriter(random(), dir, new MockAnalyzer(random())); + + Document doc1 = new Document(); + doc1.add(newStringField("f1", "1", Field.Store.NO)); + doc1.add(newTextField("f2", "a b c d e", Field.Store.NO)); + writer.addDocument(doc1); + + Document doc2 = new Document(); + doc2.add(newStringField("f1", "2", Field.Store.NO)); + doc2.add(new TextField("f2", "a c", Field.Store.NO)); + writer.addDocument(doc2); + + Document doc3 = new Document(); + doc3.add(newStringField("f1", "3", Field.Store.NO)); + doc3.add(newTextField("f2", "a f", Field.Store.NO)); + writer.addDocument(doc3); + + Map userData = ImmutableMap.of("data", "val"); + writer.w.setLiveCommitData(userData.entrySet()); + + writer.commit(); + + writer.close(); + dir.close(); + + return indexDir; + } + + @Override + @After + public void tearDown() throws Exception { + super.tearDown(); + reader.close(); + dir.close(); + } + +} diff --git a/lucene/luke/src/test/org/apache/lucene/luke/models/overview/TermCountsTest.java b/lucene/luke/src/test/org/apache/lucene/luke/models/overview/TermCountsTest.java new file mode 100644 index 000000000000..0ccfd5e67cef --- /dev/null +++ b/lucene/luke/src/test/org/apache/lucene/luke/models/overview/TermCountsTest.java @@ -0,0 +1,82 @@ +/* + * 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.models.overview; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Map; + +import org.junit.Test; + +public class TermCountsTest extends OverviewTestBase { + + @Test + public void testNumTerms() throws Exception { + TermCounts termCounts = new TermCounts(reader); + assertEquals(9, termCounts.numTerms()); + } + + @Test + @SuppressWarnings("unchecked") + public void testSortedTermCounts_count_asc() throws Exception { + TermCounts termCounts = new TermCounts(reader); + + Map countsMap = termCounts.sortedTermCounts(TermCountsOrder.COUNT_ASC); + assertEquals(Arrays.asList("f1", "f2"), new ArrayList<>(countsMap.keySet())); + + assertEquals(3, (long) countsMap.get("f1")); + assertEquals(6, (long) countsMap.get("f2")); + } + + @Test + @SuppressWarnings("unchecked") + public void testSortedTermCounts_count_desc() throws Exception { + TermCounts termCounts = new TermCounts(reader); + + Map countsMap = termCounts.sortedTermCounts(TermCountsOrder.COUNT_DESC); + assertEquals(Arrays.asList("f2", "f1"), new ArrayList<>(countsMap.keySet())); + + assertEquals(3, (long) countsMap.get("f1")); + assertEquals(6, (long) countsMap.get("f2")); + } + + @Test + @SuppressWarnings("unchecked") + public void testSortedTermCounts_name_asc() throws Exception { + TermCounts termCounts = new TermCounts(reader); + + Map countsMap = termCounts.sortedTermCounts(TermCountsOrder.NAME_ASC); + assertEquals(Arrays.asList("f1", "f2"), new ArrayList<>(countsMap.keySet())); + + assertEquals(3, (long) countsMap.get("f1")); + assertEquals(6, (long) countsMap.get("f2")); + } + + @Test + @SuppressWarnings("unchecked") + public void testSortedTermCounts_name_desc() throws Exception { + TermCounts termCounts = new TermCounts(reader); + + Map countsMap = termCounts.sortedTermCounts(TermCountsOrder.NAME_DESC); + assertEquals(Arrays.asList("f2", "f1"), new ArrayList<>(countsMap.keySet())); + + assertEquals(3, (long) countsMap.get("f1")); + assertEquals(6, (long) countsMap.get("f2")); + } + +} \ No newline at end of file diff --git a/lucene/luke/src/test/org/apache/lucene/luke/models/overview/TopTermsTest.java b/lucene/luke/src/test/org/apache/lucene/luke/models/overview/TopTermsTest.java new file mode 100644 index 000000000000..a726ad87a33b --- /dev/null +++ b/lucene/luke/src/test/org/apache/lucene/luke/models/overview/TopTermsTest.java @@ -0,0 +1,40 @@ +/* + * 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.models.overview; + +import java.util.List; + +import org.junit.Test; + +public class TopTermsTest extends OverviewTestBase { + + @Test + public void testGetTopTerms() throws Exception { + TopTerms topTerms = new TopTerms(reader); + List result = topTerms.getTopTerms("f2", 2); + + assertEquals("a", result.get(0).getDecodedTermText()); + assertEquals(3, result.get(0).getDocFreq()); + assertEquals("f2", result.get(0).getField()); + + assertEquals("c", result.get(1).getDecodedTermText()); + assertEquals(2, result.get(1).getDocFreq()); + assertEquals("f2", result.get(1).getField()); + } + +} diff --git a/lucene/luke/src/test/org/apache/lucene/luke/models/search/SearchImplTest.java b/lucene/luke/src/test/org/apache/lucene/luke/models/search/SearchImplTest.java new file mode 100644 index 000000000000..1e3f93699a28 --- /dev/null +++ b/lucene/luke/src/test/org/apache/lucene/luke/models/search/SearchImplTest.java @@ -0,0 +1,381 @@ +/* + * 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.models.search; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.apache.lucene.analysis.standard.StandardAnalyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.DoubleDocValuesField; +import org.apache.lucene.document.DoublePoint; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.FloatDocValuesField; +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.index.DirectoryReader; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.luke.models.LukeException; +import org.apache.lucene.queryparser.classic.QueryParser; +import org.apache.lucene.search.PointRangeQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.Sort; +import org.apache.lucene.search.SortField; +import org.apache.lucene.search.SortedNumericSortField; +import org.apache.lucene.search.SortedSetSortField; +import org.apache.lucene.store.Directory; +import org.apache.lucene.util.BytesRef; +import org.apache.lucene.util.LuceneTestCase; +import org.junit.Test; + +public class SearchImplTest extends LuceneTestCase { + + private IndexReader reader; + private Directory dir; + private Path indexDir; + + @Override + public void setUp() throws Exception { + super.setUp(); + createIndex(); + dir = newFSDirectory(indexDir); + reader = DirectoryReader.open(dir); + + } + + private void createIndex() throws IOException { + indexDir = createTempDir("testIndex"); + + Directory dir = newFSDirectory(indexDir); + RandomIndexWriter writer = new RandomIndexWriter(random(), dir, new StandardAnalyzer()); + + for (int i = 0; i < 10; i++) { + Document doc1 = new Document(); + doc1.add(newTextField("f1", "Apple Pie", Field.Store.YES)); + doc1.add(new SortedDocValuesField("f2", new BytesRef("a" + (i * 10 + 1)))); + doc1.add(new SortedSetDocValuesField("f3", new BytesRef("a" + (i * 10 + 1)))); + doc1.add(new NumericDocValuesField("f4", i * 10 + 1L)); + doc1.add(new FloatDocValuesField("f5", i * 10 + 1.0f)); + doc1.add(new DoubleDocValuesField("f6", i * 10 + 1.0)); + doc1.add(new SortedNumericDocValuesField("f7", i * 10 + 1L)); + doc1.add(new IntPoint("f8", i * 10 + 1)); + doc1.add(new LongPoint("f9", i * 10 + 1L)); + doc1.add(new FloatPoint("f10", i * 10 + 1.0f)); + doc1.add(new DoublePoint("f11", i * 10 + 1.0)); + writer.addDocument(doc1); + + Document doc2 = new Document(); + doc2.add(newTextField("f1", "Brownie", Field.Store.YES)); + doc2.add(new SortedDocValuesField("f2", new BytesRef("b" + (i * 10 + 2)))); + doc2.add(new SortedSetDocValuesField("f3", new BytesRef("b" + (i * 10 + 2)))); + doc2.add(new NumericDocValuesField("f4", i * 10 + 2L)); + doc2.add(new FloatDocValuesField("f5", i * 10 + 2.0f)); + doc2.add(new DoubleDocValuesField("f6", i * 10 + 2.0)); + doc2.add(new SortedNumericDocValuesField("f7", i * 10 + 2L)); + doc2.add(new IntPoint("f8", i * 10 + 2)); + doc2.add(new LongPoint("f9", i * 10 + 2L)); + doc2.add(new FloatPoint("f10", i * 10 + 2.0f)); + doc2.add(new DoublePoint("f11", i * 10 + 2.0)); + writer.addDocument(doc2); + + Document doc3 = new Document(); + doc3.add(newTextField("f1", "Chocolate Pie", Field.Store.YES)); + doc3.add(new SortedDocValuesField("f2", new BytesRef("c" + (i * 10 + 3)))); + doc3.add(new SortedSetDocValuesField("f3", new BytesRef("c" + (i * 10 + 3)))); + doc3.add(new NumericDocValuesField("f4", i * 10 + 3L)); + doc3.add(new FloatDocValuesField("f5", i * 10 + 3.0f)); + doc3.add(new DoubleDocValuesField("f6", i * 10 + 3.0)); + doc3.add(new SortedNumericDocValuesField("f7", i * 10 + 3L)); + doc3.add(new IntPoint("f8", i * 10 + 3)); + doc3.add(new LongPoint("f9", i * 10 + 3L)); + doc3.add(new FloatPoint("f10", i * 10 + 3.0f)); + doc3.add(new DoublePoint("f11", i * 10 + 3.0)); + writer.addDocument(doc3); + + Document doc4 = new Document(); + doc4.add(newTextField("f1", "Doughnut", Field.Store.YES)); + doc4.add(new SortedDocValuesField("f2", new BytesRef("d" + (i * 10 + 4)))); + doc4.add(new SortedSetDocValuesField("f3", new BytesRef("d" + (i * 10 + 4)))); + doc4.add(new NumericDocValuesField("f4", i * 10 + 4L)); + doc4.add(new FloatDocValuesField("f5", i * 10 + 4.0f)); + doc4.add(new DoubleDocValuesField("f6", i * 10 + 4.0)); + doc4.add(new SortedNumericDocValuesField("f7", i * 10 + 4L)); + doc4.add(new IntPoint("f8", i * 10 + 4)); + doc4.add(new LongPoint("f9", i * 10 + 4L)); + doc4.add(new FloatPoint("f10", i * 10 + 4.0f)); + doc4.add(new DoublePoint("f11", i * 10 + 4.0)); + writer.addDocument(doc4); + + Document doc5 = new Document(); + doc5.add(newTextField("f1", "Eclair", Field.Store.YES)); + doc5.add(new SortedDocValuesField("f2", new BytesRef("e" + (i * 10 + 5)))); + doc5.add(new SortedSetDocValuesField("f3", new BytesRef("e" + (i * 10 + 5)))); + doc5.add(new NumericDocValuesField("f4", i * 10 + 5L)); + doc5.add(new FloatDocValuesField("f5", i * 10 + 5.0f)); + doc5.add(new DoubleDocValuesField("f6", i * 10 + 5.0)); + doc5.add(new SortedNumericDocValuesField("f7", i * 10 + 5L)); + doc5.add(new IntPoint("f8", i * 10 + 5)); + doc5.add(new LongPoint("f9", i * 10 + 5L)); + doc5.add(new FloatPoint("f10", i * 10 + 5.0f)); + doc5.add(new DoublePoint("f11", i * 10 + 5.0)); + writer.addDocument(doc5); + } + writer.commit(); + writer.close(); + dir.close(); + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + reader.close(); + dir.close(); + } + + @Test + public void testGetSortableFieldNames() { + SearchImpl search = new SearchImpl(reader); + assertArrayEquals(new String[]{"f2", "f3", "f4", "f5", "f6", "f7"}, + search.getSortableFieldNames().toArray()); + } + + @Test + public void testGetSearchableFieldNames() { + SearchImpl search = new SearchImpl(reader); + assertArrayEquals(new String[]{"f1"}, + search.getSearchableFieldNames().toArray()); + } + + @Test + public void testGetRangeSearchableFieldNames() { + SearchImpl search = new SearchImpl(reader); + assertArrayEquals(new String[]{"f8", "f9", "f10", "f11"}, search.getRangeSearchableFieldNames().toArray()); + } + + @Test + public void testParseClassic() { + SearchImpl search = new SearchImpl(reader); + QueryParserConfig config = new QueryParserConfig.Builder() + .allowLeadingWildcard(true) + .defaultOperator(QueryParserConfig.Operator.AND) + .fuzzyMinSim(1.0f) + .build(); + Query q = search.parseQuery("app~ f2:*ie", "f1", new StandardAnalyzer(), + config, false); + assertEquals("+f1:app~1 +f2:*ie", q.toString()); + } + + @Test + public void testParsePointRange() { + SearchImpl search = new SearchImpl(reader); + Map> types = new HashMap<>(); + types.put("f8", Integer.class); + + QueryParserConfig config = new QueryParserConfig.Builder() + .useClassicParser(false) + .typeMap(types) + .build(); + Query q = search.parseQuery("f8:[10 TO 20]", "f1", new StandardAnalyzer(), + config, false); + assertEquals("f8:[10 TO 20]", q.toString()); + assertTrue(q instanceof PointRangeQuery); + } + + @Test + public void testGuessSortTypes() { + SearchImpl search = new SearchImpl(reader); + + assertTrue(search.guessSortTypes("f1").isEmpty()); + + assertArrayEquals( + new SortField[]{ + new SortField("f2", SortField.Type.STRING), + new SortField("f2", SortField.Type.STRING_VAL)}, + search.guessSortTypes("f2").toArray()); + + assertArrayEquals( + new SortField[]{new SortedSetSortField("f3", false)}, + search.guessSortTypes("f3").toArray()); + + assertArrayEquals( + new SortField[]{ + new SortField("f4", SortField.Type.INT), + new SortField("f4", SortField.Type.LONG), + new SortField("f4", SortField.Type.FLOAT), + new SortField("f4", SortField.Type.DOUBLE)}, + search.guessSortTypes("f4").toArray()); + + assertArrayEquals( + new SortField[]{ + new SortField("f5", SortField.Type.INT), + new SortField("f5", SortField.Type.LONG), + new SortField("f5", SortField.Type.FLOAT), + new SortField("f5", SortField.Type.DOUBLE)}, + search.guessSortTypes("f5").toArray()); + + assertArrayEquals( + new SortField[]{ + new SortField("f6", SortField.Type.INT), + new SortField("f6", SortField.Type.LONG), + new SortField("f6", SortField.Type.FLOAT), + new SortField("f6", SortField.Type.DOUBLE)}, + search.guessSortTypes("f6").toArray()); + + assertArrayEquals( + new SortField[]{ + new SortedNumericSortField("f7", SortField.Type.INT), + new SortedNumericSortField("f7", SortField.Type.LONG), + new SortedNumericSortField("f7", SortField.Type.FLOAT), + new SortedNumericSortField("f7", SortField.Type.DOUBLE)}, + search.guessSortTypes("f7").toArray()); + } + + @Test(expected = LukeException.class) + public void testGuessSortTypesNoSuchField() { + SearchImpl search = new SearchImpl(reader); + search.guessSortTypes("unknown"); + } + + @Test + public void testGetSortType() { + SearchImpl search = new SearchImpl(reader); + + assertFalse(search.getSortType("f1", "STRING", false).isPresent()); + + assertEquals(new SortField("f2", SortField.Type.STRING, false), + search.getSortType("f2", "STRING", false).get()); + assertFalse(search.getSortType("f2", "INT", false).isPresent()); + + assertEquals(new SortedSetSortField("f3", false), + search.getSortType("f3", "CUSTOM", false).get()); + + assertEquals(new SortField("f4", SortField.Type.LONG, false), + search.getSortType("f4", "LONG", false).get()); + assertFalse(search.getSortType("f4", "STRING", false).isPresent()); + + assertEquals(new SortField("f5", SortField.Type.FLOAT, false), + search.getSortType("f5", "FLOAT", false).get()); + assertFalse(search.getSortType("f5", "STRING", false).isPresent()); + + assertEquals(new SortField("f6", SortField.Type.DOUBLE, false), + search.getSortType("f6", "DOUBLE", false).get()); + assertFalse(search.getSortType("f6", "STRING", false).isPresent()); + + assertEquals(new SortedNumericSortField("f7", SortField.Type.LONG, false), + search.getSortType("f7", "LONG", false).get()); + assertFalse(search.getSortType("f7", "STRING", false).isPresent()); + } + + @Test(expected = LukeException.class) + public void testGetSortTypeNoSuchField() { + SearchImpl search = new SearchImpl(reader); + + search.getSortType("unknown", "STRING", false); + } + + @Test + public void testSearch() throws Exception { + SearchImpl search = new SearchImpl(reader); + Query query = new QueryParser("f1", new StandardAnalyzer()).parse("apple"); + SearchResults res = search.search(query, new SimilarityConfig.Builder().build(), null, 10); + + assertEquals(10, res.getTotalHits()); + assertEquals(10, res.size()); + assertEquals(0, res.getOffset()); + } + + @Test + public void testSearchWithSort() throws Exception { + SearchImpl search = new SearchImpl(reader); + Query query = new QueryParser("f1", new StandardAnalyzer()).parse("apple"); + Sort sort = new Sort(new SortField("f2", SortField.Type.STRING, true)); + SearchResults res = search.search(query, new SimilarityConfig.Builder().build(), sort, null, 10); + + assertEquals(10, res.getTotalHits()); + assertEquals(10, res.size()); + assertEquals(0, res.getOffset()); + } + + @Test + public void testNextPage() throws Exception { + SearchImpl search = new SearchImpl(reader); + Query query = new QueryParser("f1", new StandardAnalyzer()).parse("pie"); + search.search(query, new SimilarityConfig.Builder().build(), null, 10); + Optional opt = search.nextPage(); + assertTrue(opt.isPresent()); + + SearchResults res = opt.get(); + assertEquals(20, res.getTotalHits()); + assertEquals(10, res.size()); + assertEquals(10, res.getOffset()); + } + + @Test(expected = LukeException.class) + public void testNextPageSearchNotStarted() { + SearchImpl search = new SearchImpl(reader); + search.nextPage(); + } + + @Test + public void testNextPageNoMoreResults() throws Exception { + SearchImpl search = new SearchImpl(reader); + Query query = new QueryParser("f1", new StandardAnalyzer()).parse("pie"); + search.search(query, new SimilarityConfig.Builder().build(), null, 10); + search.nextPage(); + assertFalse(search.nextPage().isPresent()); + } + + @Test + public void testPrevPage() throws Exception { + SearchImpl search = new SearchImpl(reader); + Query query = new QueryParser("f1", new StandardAnalyzer()).parse("pie"); + search.search(query, new SimilarityConfig.Builder().build(), null, 10); + search.nextPage(); + Optional opt = search.prevPage(); + assertTrue(opt.isPresent()); + + SearchResults res = opt.get(); + assertEquals(20, res.getTotalHits()); + assertEquals(10, res.size()); + assertEquals(0, res.getOffset()); + } + + @Test(expected = LukeException.class) + public void testPrevPageSearchNotStarted() { + SearchImpl search = new SearchImpl(reader); + search.prevPage(); + } + + @Test + public void testPrevPageNoMoreResults() throws Exception { + SearchImpl search = new SearchImpl(reader); + Query query = new QueryParser("f1", new StandardAnalyzer()).parse("pie"); + search.search(query, new SimilarityConfig.Builder().build(), null, 10); + assertFalse(search.prevPage().isPresent()); + } + +} diff --git a/lucene/module-build.xml b/lucene/module-build.xml index d5798debf9f0..0994696b8c7f 100644 --- a/lucene/module-build.xml +++ b/lucene/module-build.xml @@ -561,6 +561,17 @@ + + + + + + + + + + + diff --git a/lucene/tools/junit4/tests.policy b/lucene/tools/junit4/tests.policy index c698c2cd47a7..b373044c2887 100644 --- a/lucene/tools/junit4/tests.policy +++ b/lucene/tools/junit4/tests.policy @@ -66,9 +66,13 @@ grant { permission java.lang.RuntimePermission "accessClassInPackage.org.apache.xerces.util"; // needed by jacoco to dump coverage permission java.lang.RuntimePermission "shutdownHooks"; + // needed by org.apache.logging.log4j + permission java.lang.RuntimePermission "getenv.*"; + permission java.lang.RuntimePermission "getClassLoader"; + permission javax.management.MBeanServerPermission "createMBeanServer"; // read access to all system properties: - permission java.util.PropertyPermission "*", "read"; + permission java.util.PropertyPermission "*", "read,write"; // write access to only these: // locale randomization permission java.util.PropertyPermission "user.language", "write"; From 5514feaf5b0ff901aecd19cee0fbab8ed098630a Mon Sep 17 00:00:00 2001 From: Tomoko Uchida Date: Tue, 4 Dec 2018 23:12:00 +0900 Subject: [PATCH 02/12] Add the license files. --- lucene/licenses/aopalliance-1.0.jar.sha1 | 1 + lucene/licenses/aopalliance-LICENSE-PD.txt | 1 + lucene/licenses/dom4j-1.6.1.jar.sha1 | 1 + lucene/licenses/dom4j-LICENSE-BSD_LIKE.txt | 39 ++ lucene/licenses/dom4j-NOTICE.txt | 2 + .../elegant-icon-font-LICENSE-MIT.txt | 21 + lucene/licenses/elegant-icon-font-NOTICE.txt | 3 + lucene/licenses/guava-19.0.jar.sha1 | 1 + lucene/licenses/guava-LICENSE-ASL.txt | 202 ++++++++++ lucene/licenses/guava-NOTICE.txt | 0 lucene/licenses/guice-4.1.0.jar.sha1 | 1 + lucene/licenses/guice-LICENSE-ASL.txt | 202 ++++++++++ lucene/licenses/guice-NOTICE.txt | 0 lucene/licenses/httpclient-4.5.3.jar.sha1 | 1 + lucene/licenses/httpcore-4.4.6.jar.sha1 | 1 + lucene/licenses/ini4j-0.5.4.jar.sha1 | 1 + lucene/licenses/ini4j-LICENSE-ASL.txt | 201 ++++++++++ lucene/licenses/ini4j-NOTICE.txt | 0 lucene/licenses/javassist-3.12.1.GA.jar.sha1 | 1 + lucene/licenses/javassist-LICENSE-MPL.txt | 373 ++++++++++++++++++ lucene/licenses/javax.inject-1.jar.sha1 | 1 + lucene/licenses/javax.inject-LICENSE-ASL.txt | 202 ++++++++++ lucene/licenses/javax.inject-NOTICE.txt | 5 + ...tty-continuation-9.4.11.v20180605.jar.sha1 | 1 + .../jetty-http-9.4.11.v20180605.jar.sha1 | 1 + .../jetty-io-9.4.11.v20180605.jar.sha1 | 1 + .../jetty-server-9.4.11.v20180605.jar.sha1 | 1 + .../jetty-servlet-9.4.11.v20180605.jar.sha1 | 1 + .../jetty-util-9.4.11.v20180605.jar.sha1 | 1 + lucene/licenses/jsr305-3.0.1.jar.sha1 | 1 + lucene/licenses/jsr305-LICENSE-BSD.txt | 8 + lucene/licenses/jsr305-NOTICE.txt | 0 lucene/licenses/log4j-1.2-api-2.11.0.jar.sha1 | 1 + lucene/licenses/log4j-1.2-api-LICENSE-ASL.txt | 202 ++++++++++ lucene/licenses/log4j-1.2-api-NOTICE.txt | 0 lucene/licenses/log4j-LICENSE-ASL.txt | 202 ++++++++++ lucene/licenses/log4j-NOTICE.txt | 0 lucene/licenses/log4j-api-2.11.0.jar.sha1 | 1 + lucene/licenses/log4j-api-LICENSE-ASL.txt | 202 ++++++++++ lucene/licenses/log4j-api-NOTICE.txt | 0 lucene/licenses/log4j-core-2.11.0.jar.sha1 | 1 + lucene/licenses/log4j-core-LICENSE-ASL.txt | 202 ++++++++++ lucene/licenses/log4j-core-NOTICE.txt | 0 lucene/licenses/log4j-slf4j-LICENSE-ASL.txt | 202 ++++++++++ lucene/licenses/log4j-slf4j-NOTICE.txt | 0 .../licenses/log4j-slf4j-impl-2.11.0.jar.sha1 | 1 + lucene/licenses/reflections-0.9.8.jar.sha1 | 1 + lucene/licenses/reflections-LICENSE-PD.txt | 14 + lucene/licenses/xml-apis-1.0.b2.jar.sha1 | 1 + lucene/licenses/xml-apis-LICENSE-ASL.txt | 202 ++++++++++ lucene/licenses/xml-apis-NOTICE.txt | 0 51 files changed, 2507 insertions(+) create mode 100644 lucene/licenses/aopalliance-1.0.jar.sha1 create mode 100644 lucene/licenses/aopalliance-LICENSE-PD.txt create mode 100644 lucene/licenses/dom4j-1.6.1.jar.sha1 create mode 100644 lucene/licenses/dom4j-LICENSE-BSD_LIKE.txt create mode 100644 lucene/licenses/dom4j-NOTICE.txt create mode 100644 lucene/licenses/elegant-icon-font-LICENSE-MIT.txt create mode 100644 lucene/licenses/elegant-icon-font-NOTICE.txt create mode 100644 lucene/licenses/guava-19.0.jar.sha1 create mode 100644 lucene/licenses/guava-LICENSE-ASL.txt create mode 100644 lucene/licenses/guava-NOTICE.txt create mode 100644 lucene/licenses/guice-4.1.0.jar.sha1 create mode 100644 lucene/licenses/guice-LICENSE-ASL.txt create mode 100644 lucene/licenses/guice-NOTICE.txt create mode 100644 lucene/licenses/httpclient-4.5.3.jar.sha1 create mode 100644 lucene/licenses/httpcore-4.4.6.jar.sha1 create mode 100644 lucene/licenses/ini4j-0.5.4.jar.sha1 create mode 100644 lucene/licenses/ini4j-LICENSE-ASL.txt create mode 100644 lucene/licenses/ini4j-NOTICE.txt create mode 100644 lucene/licenses/javassist-3.12.1.GA.jar.sha1 create mode 100644 lucene/licenses/javassist-LICENSE-MPL.txt create mode 100644 lucene/licenses/javax.inject-1.jar.sha1 create mode 100644 lucene/licenses/javax.inject-LICENSE-ASL.txt create mode 100644 lucene/licenses/javax.inject-NOTICE.txt create mode 100644 lucene/licenses/jetty-continuation-9.4.11.v20180605.jar.sha1 create mode 100644 lucene/licenses/jetty-http-9.4.11.v20180605.jar.sha1 create mode 100644 lucene/licenses/jetty-io-9.4.11.v20180605.jar.sha1 create mode 100644 lucene/licenses/jetty-server-9.4.11.v20180605.jar.sha1 create mode 100644 lucene/licenses/jetty-servlet-9.4.11.v20180605.jar.sha1 create mode 100644 lucene/licenses/jetty-util-9.4.11.v20180605.jar.sha1 create mode 100644 lucene/licenses/jsr305-3.0.1.jar.sha1 create mode 100644 lucene/licenses/jsr305-LICENSE-BSD.txt create mode 100644 lucene/licenses/jsr305-NOTICE.txt create mode 100644 lucene/licenses/log4j-1.2-api-2.11.0.jar.sha1 create mode 100644 lucene/licenses/log4j-1.2-api-LICENSE-ASL.txt create mode 100644 lucene/licenses/log4j-1.2-api-NOTICE.txt create mode 100644 lucene/licenses/log4j-LICENSE-ASL.txt create mode 100644 lucene/licenses/log4j-NOTICE.txt create mode 100644 lucene/licenses/log4j-api-2.11.0.jar.sha1 create mode 100644 lucene/licenses/log4j-api-LICENSE-ASL.txt create mode 100644 lucene/licenses/log4j-api-NOTICE.txt create mode 100644 lucene/licenses/log4j-core-2.11.0.jar.sha1 create mode 100644 lucene/licenses/log4j-core-LICENSE-ASL.txt create mode 100644 lucene/licenses/log4j-core-NOTICE.txt create mode 100644 lucene/licenses/log4j-slf4j-LICENSE-ASL.txt create mode 100644 lucene/licenses/log4j-slf4j-NOTICE.txt create mode 100644 lucene/licenses/log4j-slf4j-impl-2.11.0.jar.sha1 create mode 100644 lucene/licenses/reflections-0.9.8.jar.sha1 create mode 100644 lucene/licenses/reflections-LICENSE-PD.txt create mode 100644 lucene/licenses/xml-apis-1.0.b2.jar.sha1 create mode 100644 lucene/licenses/xml-apis-LICENSE-ASL.txt create mode 100644 lucene/licenses/xml-apis-NOTICE.txt diff --git a/lucene/licenses/aopalliance-1.0.jar.sha1 b/lucene/licenses/aopalliance-1.0.jar.sha1 new file mode 100644 index 000000000000..5da3c21c7f4b --- /dev/null +++ b/lucene/licenses/aopalliance-1.0.jar.sha1 @@ -0,0 +1 @@ +0235ba8b489512805ac13a8f9ea77a1ca5ebe3e8 diff --git a/lucene/licenses/aopalliance-LICENSE-PD.txt b/lucene/licenses/aopalliance-LICENSE-PD.txt new file mode 100644 index 000000000000..a577e7ebfe62 --- /dev/null +++ b/lucene/licenses/aopalliance-LICENSE-PD.txt @@ -0,0 +1 @@ +LICENCE: all the source code provided by AOP Alliance is Public Domain. \ No newline at end of file diff --git a/lucene/licenses/dom4j-1.6.1.jar.sha1 b/lucene/licenses/dom4j-1.6.1.jar.sha1 new file mode 100644 index 000000000000..def171f337f9 --- /dev/null +++ b/lucene/licenses/dom4j-1.6.1.jar.sha1 @@ -0,0 +1 @@ +5d3ccc056b6f056dbf0dddfdf43894b9065a8f94 diff --git a/lucene/licenses/dom4j-LICENSE-BSD_LIKE.txt b/lucene/licenses/dom4j-LICENSE-BSD_LIKE.txt new file mode 100644 index 000000000000..b9fa09a3c9e6 --- /dev/null +++ b/lucene/licenses/dom4j-LICENSE-BSD_LIKE.txt @@ -0,0 +1,39 @@ +Copyright 2001-2016 (C) MetaStuff, Ltd. and DOM4J contributors. All Rights Reserved. + +Redistribution and use of this software and associated documentation +("Software"), with or without modification, are permitted provided +that the following conditions are met: + +1. Redistributions of source code must retain copyright + statements and notices. Redistributions must also contain a + copy of this document. + +2. Redistributions in binary form must reproduce the + above copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +3. The name "DOM4J" must not be used to endorse or promote + products derived from this Software without prior written + permission of MetaStuff, Ltd. For written permission, + please contact dom4j-info@metastuff.com. + +4. Products derived from this Software may not be called "DOM4J" + nor may "DOM4J" appear in their names without prior written + permission of MetaStuff, Ltd. DOM4J is a registered + trademark of MetaStuff, Ltd. + +5. Due credit should be given to the DOM4J Project - https://dom4j.github.io/ + +THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT +NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lucene/licenses/dom4j-NOTICE.txt b/lucene/licenses/dom4j-NOTICE.txt new file mode 100644 index 000000000000..effab0e5bb0e --- /dev/null +++ b/lucene/licenses/dom4j-NOTICE.txt @@ -0,0 +1,2 @@ +Copyright 2001-2016 (C) MetaStuff, Ltd. and DOM4J contributors. All Rights Reserved. + 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 000000000000..effefee5f0ce --- /dev/null +++ b/lucene/licenses/elegant-icon-font-LICENSE-MIT.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) <2013> + +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 000000000000..ea97d9b601c7 --- /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/guava-19.0.jar.sha1 b/lucene/licenses/guava-19.0.jar.sha1 new file mode 100644 index 000000000000..dba40a2f76b6 --- /dev/null +++ b/lucene/licenses/guava-19.0.jar.sha1 @@ -0,0 +1 @@ +6ce200f6b23222af3d8abb6b6459e6c44f4bb0e9 diff --git a/lucene/licenses/guava-LICENSE-ASL.txt b/lucene/licenses/guava-LICENSE-ASL.txt new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/lucene/licenses/guava-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/guava-NOTICE.txt b/lucene/licenses/guava-NOTICE.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/lucene/licenses/guice-4.1.0.jar.sha1 b/lucene/licenses/guice-4.1.0.jar.sha1 new file mode 100644 index 000000000000..0d568b2dde89 --- /dev/null +++ b/lucene/licenses/guice-4.1.0.jar.sha1 @@ -0,0 +1 @@ +eeb69005da379a10071aa4948c48d89250febb07 diff --git a/lucene/licenses/guice-LICENSE-ASL.txt b/lucene/licenses/guice-LICENSE-ASL.txt new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/lucene/licenses/guice-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/guice-NOTICE.txt b/lucene/licenses/guice-NOTICE.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/lucene/licenses/httpclient-4.5.3.jar.sha1 b/lucene/licenses/httpclient-4.5.3.jar.sha1 new file mode 100644 index 000000000000..415a080e3be1 --- /dev/null +++ b/lucene/licenses/httpclient-4.5.3.jar.sha1 @@ -0,0 +1 @@ +d1577ae15f01ef5438c5afc62162457c00a34713 diff --git a/lucene/licenses/httpcore-4.4.6.jar.sha1 b/lucene/licenses/httpcore-4.4.6.jar.sha1 new file mode 100644 index 000000000000..83cac99365fe --- /dev/null +++ b/lucene/licenses/httpcore-4.4.6.jar.sha1 @@ -0,0 +1 @@ +e3fd8ced1f52c7574af952e2e6da0df8df08eb82 diff --git a/lucene/licenses/ini4j-0.5.4.jar.sha1 b/lucene/licenses/ini4j-0.5.4.jar.sha1 new file mode 100644 index 000000000000..8e40e0600d1b --- /dev/null +++ b/lucene/licenses/ini4j-0.5.4.jar.sha1 @@ -0,0 +1 @@ +4a3ee4146a90c619b20977d65951825f5675b560 diff --git a/lucene/licenses/ini4j-LICENSE-ASL.txt b/lucene/licenses/ini4j-LICENSE-ASL.txt new file mode 100644 index 000000000000..f49a4e16e68b --- /dev/null +++ b/lucene/licenses/ini4j-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/ini4j-NOTICE.txt b/lucene/licenses/ini4j-NOTICE.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/lucene/licenses/javassist-3.12.1.GA.jar.sha1 b/lucene/licenses/javassist-3.12.1.GA.jar.sha1 new file mode 100644 index 000000000000..f019c40010ad --- /dev/null +++ b/lucene/licenses/javassist-3.12.1.GA.jar.sha1 @@ -0,0 +1 @@ +526633327faa61aee448a519e8a4d53ec3057885 diff --git a/lucene/licenses/javassist-LICENSE-MPL.txt b/lucene/licenses/javassist-LICENSE-MPL.txt new file mode 100644 index 000000000000..7d842b40bcb6 --- /dev/null +++ b/lucene/licenses/javassist-LICENSE-MPL.txt @@ -0,0 +1,373 @@ + + +Javassist License + + + + +

MOZILLA PUBLIC LICENSE
Version +1.1 +

+


+
+

1. Definitions. +

    1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. +

    1.1. ''Contributor'' means each entity that creates or contributes + to the creation of Modifications. +

    1.2. ''Contributor Version'' means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications made by + that particular Contributor. +

    1.3. ''Covered Code'' means the Original Code or Modifications or + the combination of the Original Code and Modifications, in each case including + portions thereof. +

    1.4. ''Electronic Distribution Mechanism'' means a mechanism + generally accepted in the software development community for the electronic + transfer of data. +

    1.5. ''Executable'' means Covered Code in any form other than Source + Code. +

    1.6. ''Initial Developer'' means the individual or entity identified + as the Initial Developer in the Source Code notice required by Exhibit + A. +

    1.7. ''Larger Work'' means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. +

    1.8. ''License'' means this document. +

    1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or subsequently + acquired, any and all of the rights conveyed herein. +

    1.9. ''Modifications'' means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: +

      A. Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. +

      B. Any new file that contains any part of the Original Code or + previous Modifications.
       

    1.10. ''Original Code'' + means Source Code of computer software code which is described in the Source + Code notice required by Exhibit A as Original Code, and which, at the + time of its release under this License is not already Covered Code governed by + this License. +

    1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation,  method, process, and + apparatus claims, in any patent Licensable by grantor. +

    1.11. ''Source Code'' means the preferred form of the Covered Code + for making modifications to it, including all modules it contains, plus any + associated interface definition files, scripts used to control compilation and + installation of an Executable, or source code differential comparisons against + either the Original Code or another well known, available Covered Code of the + Contributor's choice. The Source Code can be in a compressed or archival form, + provided the appropriate decompression or de-archiving software is widely + available for no charge. +

    1.12. "You'' (or "Your")  means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this License + or a future version of this License issued under Section 6.1. For legal + entities, "You'' includes any entity which controls, is controlled by, or is + under common control with You. For purposes of this definition, "control'' + means (a) the power, direct or indirect, to cause the direction or management + of such entity, whether by contract or otherwise, or (b) ownership of more + than fifty percent (50%) of the outstanding shares or beneficial ownership of + such entity.

2. Source Code License. +
    2.1. The Initial Developer Grant.
    The Initial Developer hereby + grants You a world-wide, royalty-free, non-exclusive license, subject to third + party intellectual property claims: +
      (a)  under intellectual property rights (other than + patent or trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original Code (or + portions thereof) with or without Modifications, and/or as part of a Larger + Work; and +

      (b) under Patents Claims infringed by the making, using or selling + of Original Code, to make, have made, use, practice, sell, and offer for + sale, and/or otherwise dispose of the Original Code (or portions thereof). +

        +
        (c) the licenses granted in this Section 2.1(a) and (b) + are effective on the date Initial Developer first distributes Original Code + under the terms of this License. +

        (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) separate + from the Original Code;  or 3) for infringements caused by: i) the + modification of the Original Code or ii) the combination of the Original + Code with other software or devices.
         

      2.2. Contributor + Grant.
      Subject to third party intellectual property claims, each + Contributor hereby grants You a world-wide, royalty-free, non-exclusive + license +

        (a)  under intellectual property rights (other + than patent or trademark) Licensable by Contributor, to use, reproduce, + modify, display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an unmodified + basis, with other Modifications, as Covered Code and/or as part of a Larger + Work; and +

        (b) under Patent Claims infringed by the making, using, or selling + of  Modifications made by that Contributor either alone and/or in combination with its Contributor Version (or portions of such + combination), to make, use, sell, offer for sale, have made, and/or + otherwise dispose of: 1) Modifications made by that Contributor (or portions + thereof); and 2) the combination of  Modifications made by that + Contributor with its Contributor Version (or portions of such + combination). +

        (c) the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of the Covered + Code. +

        (d)    Notwithstanding Section 2.2(b) above, no + patent license is granted: 1) for any code that Contributor has deleted from + the Contributor Version; 2)  separate from the Contributor + Version;  3)  for infringements caused by: i) third party + modifications of Contributor Version or ii)  the combination of + Modifications made by that Contributor with other software  (except as + part of the Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by that + Contributor.

    +


    3. Distribution Obligations. +

      3.1. Application of License.
      The Modifications which You create + or to which You contribute are governed by the terms of this License, + including without limitation Section 2.2. The Source Code version of + Covered Code may be distributed only under the terms of this License or a + future version of this License released under Section 6.1, and You must + include a copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code version + that alters or restricts the applicable version of this License or the + recipients' rights hereunder. However, You may include an additional document + offering the additional rights described in Section 3.5. +

      3.2. Availability of Source Code.
      Any Modification which You + create or to which You contribute must be made available in Source Code form + under the terms of this License either on the same media as an Executable + version or via an accepted Electronic Distribution Mechanism to anyone to whom + you made an Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) months + after the date it initially became available, or at least six (6) months after + a subsequent version of that particular Modification has been made available + to such recipients. You are responsible for ensuring that the Source Code + version remains available even if the Electronic Distribution Mechanism is + maintained by a third party. +

      3.3. Description of Modifications.
      You must cause all Covered + Code to which You contribute to contain a file documenting the changes You + made to create that Covered Code and the date of any change. You must include + a prominent statement that the Modification is derived, directly or + indirectly, from Original Code provided by the Initial Developer and including + the name of the Initial Developer in (a) the Source Code, and (b) in any + notice in an Executable version or related documentation in which You describe + the origin or ownership of the Covered Code. +

      3.4. Intellectual Property Matters +

        (a) Third Party Claims.
        If Contributor has knowledge that a + license under a third party's intellectual property rights is required to + exercise the rights granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code distribution + titled "LEGAL'' which describes the claim and the party making the claim in + sufficient detail that a recipient will know whom to contact. If Contributor + obtains such knowledge after the Modification is made available as described + in Section 3.2, Contributor shall promptly modify the LEGAL file in all + copies Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) reasonably + calculated to inform those who received the Covered Code that new knowledge + has been obtained. +

        (b) Contributor APIs.
        If Contributor's Modifications include + an application programming interface and Contributor has knowledge of patent + licenses which are reasonably necessary to implement that API, Contributor + must also include this information in the LEGAL file. +
         

                + (c)    Representations. +
        Contributor represents that, except as disclosed pursuant to Section + 3.4(a) above, Contributor believes that Contributor's Modifications are + Contributor's original creation(s) and/or Contributor has sufficient rights + to grant the rights conveyed by this License.
      +


      3.5. Required Notices.
      You must duplicate the notice in + Exhibit A in each file of the Source Code.  If it is not possible + to put such notice in a particular Source Code file due to its structure, then + You must include such notice in a location (such as a relevant directory) + where a user would be likely to look for such a notice.  If You created + one or more Modification(s) You may add your name as a Contributor to the + notice described in Exhibit A.  You must also duplicate this + License in any documentation for the Source Code where You describe + recipients' rights or ownership rights relating to Covered Code.  You may + choose to offer, and to charge a fee for, warranty, support, indemnity or + liability obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial Developer + or any Contributor. You must make it absolutely clear than any such warranty, + support, indemnity or liability obligation is offered by You alone, and You + hereby agree to indemnify the Initial Developer and every Contributor for any + liability incurred by the Initial Developer or such Contributor as a result of + warranty, support, indemnity or liability terms You offer. +

      3.6. Distribution of Executable Versions.
      You may distribute + Covered Code in Executable form only if the requirements of Section + 3.1-3.5 have been met for that Covered Code, and if You include a + notice stating that the Source Code version of the Covered Code is available + under the terms of this License, including a description of how and where You + have fulfilled the obligations of Section 3.2. The notice must be + conspicuously included in any notice in an Executable version, related + documentation or collateral in which You describe recipients' rights relating + to the Covered Code. You may distribute the Executable version of Covered Code + or ownership rights under a license of Your choice, which may contain terms + different from this License, provided that You are in compliance with the + terms of this License and that the license for the Executable version does not + attempt to limit or alter the recipient's rights in the Source Code version + from the rights set forth in this License. If You distribute the Executable + version under a different license You must make it absolutely clear that any + terms which differ from this License are offered by You alone, not by the + Initial Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of any such terms You offer. + +

      3.7. Larger Works.
      You may create a Larger Work by combining + Covered Code with other code not governed by the terms of this License and + distribute the Larger Work as a single product. In such a case, You must make + sure the requirements of this License are fulfilled for the Covered +Code.

    4. Inability to Comply Due to Statute or Regulation. +
      If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to statute, + judicial order, or regulation then You must: (a) comply with the terms of this + License to the maximum extent possible; and (b) describe the limitations and + the code they affect. Such description must be included in the LEGAL file + described in Section 3.4 and must be included with all distributions of + the Source Code. Except to the extent prohibited by statute or regulation, + such description must be sufficiently detailed for a recipient of ordinary + skill to be able to understand it.
    5. Application of this License. +
      This License applies to code to which the Initial Developer has attached + the notice in Exhibit A and to related Covered Code.
    6. Versions +of the License. +
      6.1. New Versions.
      Netscape Communications Corporation + (''Netscape'') may publish revised and/or new versions of the License from + time to time. Each version will be given a distinguishing version number. +

      6.2. Effect of New Versions.
      Once Covered Code has been + published under a particular version of the License, You may always continue + to use it under the terms of that version. You may also choose to use such + Covered Code under the terms of any subsequent version of the License + published by Netscape. No one other than Netscape has the right to modify the + terms applicable to Covered Code created under this License. +

      6.3. Derivative Works.
      If You create or use a modified version + of this License (which you may only do in order to apply it to code which is + not already Covered Code governed by this License), You must (a) rename Your + license so that the phrases ''Mozilla'', ''MOZILLAPL'', ''MOZPL'', + ''Netscape'', "MPL", ''NPL'' or any confusingly similar phrase do not appear + in your license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license contains + terms which differ from the Mozilla Public License and Netscape Public + License. (Filling in the name of the Initial Developer, Original Code or + Contributor in the notice described in Exhibit A shall not of + themselves be deemed to be modifications of this License.)

    7. +DISCLAIMER OF WARRANTY. +
      COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS'' BASIS, WITHOUT + WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT + LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, + FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE + QUALITY AND PERFORMANCE OF THE COVERED CODE IS WITH YOU. SHOULD ANY COVERED + CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY + OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR + CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS + LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS + DISCLAIMER.
    8. TERMINATION. +
      8.1.  This License and the rights granted hereunder will + terminate automatically if You fail to comply with terms herein and fail to + cure such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall survive any + termination of this License. Provisions which, by their nature, must remain in + effect beyond the termination of this License shall survive. +

      8.2.  If You initiate litigation by asserting a patent + infringement claim (excluding declatory judgment actions) against Initial + Developer or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant")  alleging that: +

      (a)  such Participant's Contributor Version directly or + indirectly infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License shall, upon + 60 days notice from Participant terminate prospectively, unless if within 60 + days after receipt of notice You either: (i)  agree in writing to pay + Participant a mutually agreeable reasonable royalty for Your past and future + use of Modifications made by such Participant, or (ii) withdraw Your + litigation claim with respect to the Contributor Version against such + Participant.  If within 60 days of notice, a reasonable royalty and + payment arrangement are not mutually agreed upon in writing by the parties or + the litigation claim is not withdrawn, the rights granted by Participant to + You under Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. +

      (b)  any software, hardware, or device, other than such + Participant's Contributor Version, directly or indirectly infringes any + patent, then any rights granted to You by such Participant under Sections + 2.1(b) and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that Participant. +

      8.3.  If You assert a patent infringement claim against + Participant alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as by + license or settlement) prior to the initiation of patent infringement + litigation, then the reasonable value of the licenses granted by such + Participant under Sections 2.1 or 2.2 shall be taken into account in + determining the amount or value of any payment or license. +

      8.4.  In the event of termination under Sections 8.1 or 8.2 + above,  all end user license agreements (excluding distributors and + resellers) which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination.

    9. LIMITATION OF +LIABILITY. +
      UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT (INCLUDING + NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL DEVELOPER, ANY + OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, OR ANY SUPPLIER OF ANY + OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, + INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT + LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR + MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH + PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS + LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL + INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR + LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND + LIMITATION MAY NOT APPLY TO YOU.
    10. U.S. GOVERNMENT END USERS. +
      The Covered Code is a ''commercial item,'' as that term is defined in 48 + C.F.R. 2.101 (Oct. 1995), consisting of ''commercial computer software'' and + ''commercial computer software documentation,'' as such terms are used in 48 + C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. + 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government End Users + acquire Covered Code with only those rights set forth herein.
    11. +MISCELLANEOUS. +
      This License represents the complete agreement concerning subject matter + hereof. If any provision of this License is held to be unenforceable, such + provision shall be reformed only to the extent necessary to make it + enforceable. This License shall be governed by California law provisions + (except to the extent applicable law, if any, provides otherwise), excluding + its conflict-of-law provisions. With respect to disputes in which at least one + party is a citizen of, or an entity chartered or registered to do business in + the United States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern District of + California, with venue lying in Santa Clara County, California, with the + losing party responsible for costs, including without limitation, court costs + and reasonable attorneys' fees and expenses. The application of the United + Nations Convention on Contracts for the International Sale of Goods is + expressly excluded. Any law or regulation which provides that the language of + a contract shall be construed against the drafter shall not apply to this + License.
    12. RESPONSIBILITY FOR CLAIMS. +
      As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, out of its + utilization of rights under this License and You agree to work with Initial + Developer and Contributors to distribute such responsibility on an equitable + basis. Nothing herein is intended or shall be deemed to constitute any + admission of liability.
    13. MULTIPLE-LICENSED CODE. +
      Initial Developer may designate portions of the Covered Code as + "Multiple-Licensed".  "Multiple-Licensed" means that the Initial + Developer permits you to utilize portions of the Covered Code under Your + choice of the MPL or the alternative licenses, if any, specified by the + Initial Developer in the file described in Exhibit A.
    +


    EXHIBIT A -Mozilla Public License. +

      The contents of this file are subject to the Mozilla Public License + Version 1.1 (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.mozilla.org/MPL/ +

      Software distributed under the License is distributed on an "AS IS" basis, + WITHOUT WARRANTY OF
      ANY KIND, either express or implied. See the License + for the specific language governing rights and
      limitations under the + License. +

      The Original Code is Javassist. +

      The Initial Developer of the Original Code is Shigeru Chiba. + Portions created by the Initial Developer are
        + Copyright (C) 1999- Shigeru Chiba. All Rights Reserved. +

      Contributor(s): __Bill Burke, Jason T. Greene______________. + +

      Alternatively, the contents of this software may be used under the +terms of the GNU Lesser General Public License Version 2.1 or later +(the "LGPL"), or the Apache License Version 2.0 (the "AL"), +in which case the provisions of the LGPL or the AL are applicable +instead of those above. If you wish to allow use of your version of +this software only under the terms of either the LGPL or the AL, and not to allow others to +use your version of this software under the terms of the MPL, indicate +your decision by deleting the provisions above and replace them with +the notice and other provisions required by the LGPL or the AL. If you do not +delete the provisions above, a recipient may use your version of this +software under the terms of any one of the MPL, the LGPL or the AL. + +

    + + diff --git a/lucene/licenses/javax.inject-1.jar.sha1 b/lucene/licenses/javax.inject-1.jar.sha1 new file mode 100644 index 000000000000..7ef3c707b3c6 --- /dev/null +++ b/lucene/licenses/javax.inject-1.jar.sha1 @@ -0,0 +1 @@ +6975da39a7040257bd51d21a231b76c915872d38 diff --git a/lucene/licenses/javax.inject-LICENSE-ASL.txt b/lucene/licenses/javax.inject-LICENSE-ASL.txt new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/lucene/licenses/javax.inject-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/javax.inject-NOTICE.txt b/lucene/licenses/javax.inject-NOTICE.txt new file mode 100644 index 000000000000..14478a3b60f7 --- /dev/null +++ b/lucene/licenses/javax.inject-NOTICE.txt @@ -0,0 +1,5 @@ +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/jetty-continuation-9.4.11.v20180605.jar.sha1 b/lucene/licenses/jetty-continuation-9.4.11.v20180605.jar.sha1 new file mode 100644 index 000000000000..443533c7a03a --- /dev/null +++ b/lucene/licenses/jetty-continuation-9.4.11.v20180605.jar.sha1 @@ -0,0 +1 @@ +31f1e347d013356317164b86bbbc2a6ce5c5e871 diff --git a/lucene/licenses/jetty-http-9.4.11.v20180605.jar.sha1 b/lucene/licenses/jetty-http-9.4.11.v20180605.jar.sha1 new file mode 100644 index 000000000000..76a024c023a1 --- /dev/null +++ b/lucene/licenses/jetty-http-9.4.11.v20180605.jar.sha1 @@ -0,0 +1 @@ +20c35f5336befe35b0bd5c4a63e07170fe7872d7 diff --git a/lucene/licenses/jetty-io-9.4.11.v20180605.jar.sha1 b/lucene/licenses/jetty-io-9.4.11.v20180605.jar.sha1 new file mode 100644 index 000000000000..38f084900fee --- /dev/null +++ b/lucene/licenses/jetty-io-9.4.11.v20180605.jar.sha1 @@ -0,0 +1 @@ +d164de1dac18c4ca80a1b783d879c97449909c3b diff --git a/lucene/licenses/jetty-server-9.4.11.v20180605.jar.sha1 b/lucene/licenses/jetty-server-9.4.11.v20180605.jar.sha1 new file mode 100644 index 000000000000..36e39e0d1b6d --- /dev/null +++ b/lucene/licenses/jetty-server-9.4.11.v20180605.jar.sha1 @@ -0,0 +1 @@ +58353c2f27515b007fc83ae22002feb34fc24714 diff --git a/lucene/licenses/jetty-servlet-9.4.11.v20180605.jar.sha1 b/lucene/licenses/jetty-servlet-9.4.11.v20180605.jar.sha1 new file mode 100644 index 000000000000..e90c80c87026 --- /dev/null +++ b/lucene/licenses/jetty-servlet-9.4.11.v20180605.jar.sha1 @@ -0,0 +1 @@ +66d31900fcfc70e3666f0b3335b6660635154f98 diff --git a/lucene/licenses/jetty-util-9.4.11.v20180605.jar.sha1 b/lucene/licenses/jetty-util-9.4.11.v20180605.jar.sha1 new file mode 100644 index 000000000000..111b23070fb4 --- /dev/null +++ b/lucene/licenses/jetty-util-9.4.11.v20180605.jar.sha1 @@ -0,0 +1 @@ +f0f25aa2f27d618a04bc7356fa247ae4a05245b3 diff --git a/lucene/licenses/jsr305-3.0.1.jar.sha1 b/lucene/licenses/jsr305-3.0.1.jar.sha1 new file mode 100644 index 000000000000..146b44d4cda3 --- /dev/null +++ b/lucene/licenses/jsr305-3.0.1.jar.sha1 @@ -0,0 +1 @@ +f7be08ec23c21485b9b5a1cf1654c2ec8c58168d diff --git a/lucene/licenses/jsr305-LICENSE-BSD.txt b/lucene/licenses/jsr305-LICENSE-BSD.txt new file mode 100644 index 000000000000..29fae787373c --- /dev/null +++ b/lucene/licenses/jsr305-LICENSE-BSD.txt @@ -0,0 +1,8 @@ +The JSR-305 reference implementation (lib/jsr305.jar) is +distributed under the terms of the New BSD license: + + http://www.opensource.org/licenses/bsd-license.php + +See the JSR-305 home page for more information: + + http://code.google.com/p/jsr-305/ diff --git a/lucene/licenses/jsr305-NOTICE.txt b/lucene/licenses/jsr305-NOTICE.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/lucene/licenses/log4j-1.2-api-2.11.0.jar.sha1 b/lucene/licenses/log4j-1.2-api-2.11.0.jar.sha1 new file mode 100644 index 000000000000..662cac2cdd77 --- /dev/null +++ b/lucene/licenses/log4j-1.2-api-2.11.0.jar.sha1 @@ -0,0 +1 @@ +5e488f2c717964162016cd5138e2b1ea4dcc379f diff --git a/lucene/licenses/log4j-1.2-api-LICENSE-ASL.txt b/lucene/licenses/log4j-1.2-api-LICENSE-ASL.txt new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/lucene/licenses/log4j-1.2-api-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-1.2-api-NOTICE.txt b/lucene/licenses/log4j-1.2-api-NOTICE.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/lucene/licenses/log4j-LICENSE-ASL.txt b/lucene/licenses/log4j-LICENSE-ASL.txt new file mode 100644 index 000000000000..d64569567334 --- /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 000000000000..e69de29bb2d1 diff --git a/lucene/licenses/log4j-api-2.11.0.jar.sha1 b/lucene/licenses/log4j-api-2.11.0.jar.sha1 new file mode 100644 index 000000000000..47ba9da300ca --- /dev/null +++ b/lucene/licenses/log4j-api-2.11.0.jar.sha1 @@ -0,0 +1 @@ +bede79a3f150711634a3047985517431bf6499f2 diff --git a/lucene/licenses/log4j-api-LICENSE-ASL.txt b/lucene/licenses/log4j-api-LICENSE-ASL.txt new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/lucene/licenses/log4j-api-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-api-NOTICE.txt b/lucene/licenses/log4j-api-NOTICE.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/lucene/licenses/log4j-core-2.11.0.jar.sha1 b/lucene/licenses/log4j-core-2.11.0.jar.sha1 new file mode 100644 index 000000000000..59e400d7ab54 --- /dev/null +++ b/lucene/licenses/log4j-core-2.11.0.jar.sha1 @@ -0,0 +1 @@ +e6b751e02120c08702d98750f6a80bc25343b7f5 diff --git a/lucene/licenses/log4j-core-LICENSE-ASL.txt b/lucene/licenses/log4j-core-LICENSE-ASL.txt new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/lucene/licenses/log4j-core-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-core-NOTICE.txt b/lucene/licenses/log4j-core-NOTICE.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/lucene/licenses/log4j-slf4j-LICENSE-ASL.txt b/lucene/licenses/log4j-slf4j-LICENSE-ASL.txt new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/lucene/licenses/log4j-slf4j-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-slf4j-NOTICE.txt b/lucene/licenses/log4j-slf4j-NOTICE.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/lucene/licenses/log4j-slf4j-impl-2.11.0.jar.sha1 b/lucene/licenses/log4j-slf4j-impl-2.11.0.jar.sha1 new file mode 100644 index 000000000000..dc5221e27f5f --- /dev/null +++ b/lucene/licenses/log4j-slf4j-impl-2.11.0.jar.sha1 @@ -0,0 +1 @@ +9ba207b78e470fe7765ebee14f1f0336c9cbcc18 diff --git a/lucene/licenses/reflections-0.9.8.jar.sha1 b/lucene/licenses/reflections-0.9.8.jar.sha1 new file mode 100644 index 000000000000..87cfe314a5fd --- /dev/null +++ b/lucene/licenses/reflections-0.9.8.jar.sha1 @@ -0,0 +1 @@ +f723abb59bf512952bfc503838f70f81487a6993 diff --git a/lucene/licenses/reflections-LICENSE-PD.txt b/lucene/licenses/reflections-LICENSE-PD.txt new file mode 100644 index 000000000000..5a8e332545f6 --- /dev/null +++ b/lucene/licenses/reflections-LICENSE-PD.txt @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/lucene/licenses/xml-apis-1.0.b2.jar.sha1 b/lucene/licenses/xml-apis-1.0.b2.jar.sha1 new file mode 100644 index 000000000000..bf51c7a6c639 --- /dev/null +++ b/lucene/licenses/xml-apis-1.0.b2.jar.sha1 @@ -0,0 +1 @@ +3136ca936f64c9d68529f048c2618bd356bf85c9 diff --git a/lucene/licenses/xml-apis-LICENSE-ASL.txt b/lucene/licenses/xml-apis-LICENSE-ASL.txt new file mode 100644 index 000000000000..d64569567334 --- /dev/null +++ b/lucene/licenses/xml-apis-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/xml-apis-NOTICE.txt b/lucene/licenses/xml-apis-NOTICE.txt new file mode 100644 index 000000000000..e69de29bb2d1 From 6162a37819469626c97c681ce0b912d540ccdcee Mon Sep 17 00:00:00 2001 From: Tomoko Uchida Date: Tue, 4 Dec 2018 23:12:54 +0900 Subject: [PATCH 03/12] Add slf4j entry to ivy-ignore-conflicts. --- lucene/ivy-ignore-conflicts.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lucene/ivy-ignore-conflicts.properties b/lucene/ivy-ignore-conflicts.properties index 55afbc51de94..c97e4f061568 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 = 19.0 -/org.ow2.asm/asm = 5.0_BETA \ No newline at end of file +/org.ow2.asm/asm = 5.0_BETA +/org.slf4j/slf4j-api = 1.8.0-alpha2 \ No newline at end of file From 5296c8d35228f970f59d5ec5a661f345a166ca36 Mon Sep 17 00:00:00 2001 From: Tomoko Uchida Date: Tue, 4 Dec 2018 23:33:03 +0900 Subject: [PATCH 04/12] Add class description to TabUtils.java --- .../java/org/apache/lucene/luke/app/desktop/util/TabUtils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TabUtils.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TabUtils.java index 25e0cca6ec87..c3dc7a1e479c 100644 --- a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TabUtils.java +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TabUtils.java @@ -21,6 +21,7 @@ import javax.swing.UIManager; import java.awt.Graphics; +/** Tab utilities */ public class TabUtils { public static void forceTransparent(JTabbedPane tabbedPane) { From 3c1e7fe7eaf175175f673bbd7f6e6a182d33a632 Mon Sep 17 00:00:00 2001 From: Tomoko Uchida Date: Fri, 28 Dec 2018 21:41:09 +0900 Subject: [PATCH 05/12] Fix IDEA settings for luke --- dev-tools/idea/.idea/workspace.xml | 2 +- dev-tools/idea/lucene/luke/luke.iml | 11 +++++++++++ .../lucene/luke/models/documents/DocumentField.java | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/dev-tools/idea/.idea/workspace.xml b/dev-tools/idea/.idea/workspace.xml index 970d06eeac44..e45b340079a3 100644 --- a/dev-tools/idea/.idea/workspace.xml +++ b/dev-tools/idea/.idea/workspace.xml @@ -149,7 +149,7 @@ - +