| diff --git a/dev-tools/idea/.idea/ant.xml b/dev-tools/idea/.idea/ant.xml |
| index 229d83203c6..d3f96556df8 100644 |
| --- a/dev-tools/idea/.idea/ant.xml |
| +++ b/dev-tools/idea/.idea/ant.xml |
| @@ -24,6 +24,7 @@ |
| <buildFile url="file://$PROJECT_DIR$/lucene/grouping/build.xml" /> |
| <buildFile url="file://$PROJECT_DIR$/lucene/highlighter/build.xml" /> |
| <buildFile url="file://$PROJECT_DIR$/lucene/join/build.xml" /> |
| + <buildFile url="file://$PROJECT_DIR$/lucene/luke/build.xml" /> |
| <buildFile url="file://$PROJECT_DIR$/lucene/memory/build.xml" /> |
| <buildFile url="file://$PROJECT_DIR$/lucene/misc/build.xml" /> |
| <buildFile url="file://$PROJECT_DIR$/lucene/queries/build.xml" /> |
| diff --git a/dev-tools/idea/.idea/modules.xml b/dev-tools/idea/.idea/modules.xml |
| index 65b57fb03d5..4974f19668e 100644 |
| --- a/dev-tools/idea/.idea/modules.xml |
| +++ b/dev-tools/idea/.idea/modules.xml |
| @@ -30,6 +30,7 @@ |
| <module group="Lucene/Other" filepath="$PROJECT_DIR$/lucene/grouping/grouping.iml" /> |
| <module group="Lucene/Other" filepath="$PROJECT_DIR$/lucene/highlighter/highlighter.iml" /> |
| <module group="Lucene/Other" filepath="$PROJECT_DIR$/lucene/join/join.iml" /> |
| + <module group="Lucene/Other" filepath="$PROJECT_DIR$/lucene/luke/luke.iml" /> |
| <module group="Lucene/Other" filepath="$PROJECT_DIR$/lucene/memory/memory.iml" /> |
| <module group="Lucene/Other" filepath="$PROJECT_DIR$/lucene/misc/misc.iml" /> |
| <module group="Lucene/Other" filepath="$PROJECT_DIR$/lucene/queries/queries.iml" /> |
| diff --git a/dev-tools/idea/.idea/workspace.xml b/dev-tools/idea/.idea/workspace.xml |
| index 6a1fd0ad879..bbc271ee28c 100644 |
| --- a/dev-tools/idea/.idea/workspace.xml |
| +++ b/dev-tools/idea/.idea/workspace.xml |
| @@ -148,6 +148,14 @@ |
| <option name="TEST_SEARCH_SCOPE"><value defaultName="singleModule" /></option> |
| <patterns><pattern testClass=".*\.Test[^.]*|.*\.[^.]*Test" /></patterns> |
| </configuration> |
| + <configuration default="false" name="Module luke" type="JUnit" factoryName="JUnit"> |
| + <module name="luke" /> |
| + <option name="TEST_OBJECT" value="pattern" /> |
| + <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/idea-build/lucene/luke" /> |
| + <option name="VM_PARAMETERS" value="-ea -DtempDir=temp" /> |
| + <option name="TEST_SEARCH_SCOPE"><value defaultName="singleModule" /></option> |
| + <patterns><pattern testClass=".*\.Test[^.]*|.*\.[^.]*Test" /></patterns> |
| + </configuration> |
| <configuration default="false" name="Module memory" type="JUnit" factoryName="JUnit"> |
| <module name="memory" /> |
| <option name="TEST_OBJECT" value="pattern" /> |
| diff --git a/dev-tools/idea/lucene/luke/luke.iml b/dev-tools/idea/lucene/luke/luke.iml |
| new file mode 100644 |
| index 00000000000..9bd08ef4ab1 |
| --- /dev/null |
| +++ b/dev-tools/idea/lucene/luke/luke.iml |
| @@ -0,0 +1,33 @@ |
| +<?xml version="1.0" encoding="UTF-8"?> |
| +<module type="JAVA_MODULE" version="4"> |
| + <component name="NewModuleRootManager" inherit-compiler-output="false"> |
| + <output url="file://$MODULE_DIR$/../../idea-build/lucene/luke/classes/java" /> |
| + <output-test url="file://$MODULE_DIR$/../../idea-build/lucene/luke/classes/test" /> |
| + <exclude-output /> |
| + <content url="file://$MODULE_DIR$"> |
| + <sourceFolder url="file://$MODULE_DIR$/src/java" isTestSource="false" /> |
| + <sourceFolder url="file://$MODULE_DIR$/src/resources" isTestSource="false" /> |
| + <sourceFolder url="file://$MODULE_DIR$/src/test" isTestSource="true" /> |
| + <excludeFolder url="file://$MODULE_DIR$/work" /> |
| + </content> |
| + <orderEntry type="inheritedJdk" /> |
| + <orderEntry type="sourceFolder" forTests="false" /> |
| + <orderEntry type="module-library"> |
| + <library> |
| + <CLASSES> |
| + <root url="file://$MODULE_DIR$/lib" /> |
| + </CLASSES> |
| + <JAVADOC /> |
| + <SOURCES /> |
| + <jarDirectory url="file://$MODULE_DIR$/lib" recursive="false" /> |
| + </library> |
| + </orderEntry> |
| + <orderEntry type="library" scope="TEST" name="JUnit" level="project" /> |
| + <orderEntry type="module" scope="TEST" module-name="lucene-test-framework" /> |
| + <orderEntry type="module" module-name="lucene-core" /> |
| + <orderEntry type="module" module-name="analysis-common" /> |
| + <orderEntry type="module" module-name="misc" /> |
| + <orderEntry type="module" module-name="queries" /> |
| + <orderEntry type="module" module-name="queryparser" /> |
| + </component> |
| +</module> |
| diff --git a/lucene/build.xml b/lucene/build.xml |
| index 3c1439c7e26..e3cf905c971 100644 |
| --- a/lucene/build.xml |
| +++ b/lucene/build.xml |
| @@ -287,6 +287,7 @@ |
| <zipfileset prefix="lucene-${version}" dir="${build.dir}"> |
| <patternset refid="binary.build.dist.patterns"/> |
| </zipfileset> |
| + <zipfileset prefix="lucene-${version}" dir="${build.dir}" includes="**/*.sh,**/*.bat" filemode="755"/> |
| </zip> |
| <make-checksums file="${dist.dir}/lucene-${version}.zip"/> |
| </target> |
| @@ -310,6 +311,7 @@ |
| <tarfileset prefix="lucene-${version}" dir="${build.dir}"> |
| <patternset refid="binary.build.dist.patterns"/> |
| </tarfileset> |
| + <tarfileset prefix="lucene-${version}" dir="${build.dir}" includes="**/*.sh,**/*.bat" filemode="755"/> |
| </tar> |
| <make-checksums file="${dist.dir}/lucene-${version}.tgz"/> |
| </target> |
| diff --git a/lucene/ivy-ignore-conflicts.properties b/lucene/ivy-ignore-conflicts.properties |
| index 6300bdf6d6f..df3a2e5a43b 100644 |
| --- a/lucene/ivy-ignore-conflicts.properties |
| +++ b/lucene/ivy-ignore-conflicts.properties |
| @@ -10,4 +10,5 @@ |
| # trigger a conflict) when the ant check-lib-versions target is run. |
| |
| /com.google.guava/guava = 16.0.1 |
| -/org.ow2.asm/asm = 5.0_BETA |
| \ No newline at end of file |
| +/org.ow2.asm/asm = 5.0_BETA |
| + |
| diff --git a/lucene/licenses/elegant-icon-font-LICENSE-MIT.txt b/lucene/licenses/elegant-icon-font-LICENSE-MIT.txt |
| new file mode 100644 |
| index 00000000000..effefee5f0c |
| --- /dev/null |
| +++ b/lucene/licenses/elegant-icon-font-LICENSE-MIT.txt |
| @@ -0,0 +1,21 @@ |
| +The MIT License (MIT) |
| + |
| +Copyright (c) <2013> <Elegant Themes, Inc.> |
| + |
| +Permission is hereby granted, free of charge, to any person obtaining a copy |
| +of this software and associated documentation files (the "Software"), to deal |
| +in the Software without restriction, including without limitation the rights |
| +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| +copies of the Software, and to permit persons to whom the Software is |
| +furnished to do so, subject to the following conditions: |
| + |
| +The above copyright notice and this permission notice shall be included in |
| +all copies or substantial portions of the Software. |
| + |
| +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| +THE SOFTWARE. |
| \ No newline at end of file |
| diff --git a/lucene/licenses/elegant-icon-font-NOTICE.txt b/lucene/licenses/elegant-icon-font-NOTICE.txt |
| new file mode 100644 |
| index 00000000000..ea97d9b601c |
| --- /dev/null |
| +++ b/lucene/licenses/elegant-icon-font-NOTICE.txt |
| @@ -0,0 +1,3 @@ |
| +The Elegant Icon Font web page: https://www.elegantthemes.com/blog/resources/elegant-icon-font |
| + |
| +These icons are dual licensed under the GPL 2.0 and MIT, and are completely free to use. |
| diff --git a/lucene/licenses/log4j-LICENSE-ASL.txt b/lucene/licenses/log4j-LICENSE-ASL.txt |
| new file mode 100644 |
| index 00000000000..d6456956733 |
| --- /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 00000000000..d697542317c |
| --- /dev/null |
| +++ b/lucene/licenses/log4j-NOTICE.txt |
| @@ -0,0 +1,5 @@ |
| +Apache log4j |
| +Copyright 2010 The Apache Software Foundation |
| + |
| +This product includes software developed at |
| +The Apache Software Foundation (http://www.apache.org/). |
| diff --git a/lucene/licenses/log4j-api-2.11.2.jar.sha1 b/lucene/licenses/log4j-api-2.11.2.jar.sha1 |
| new file mode 100644 |
| index 00000000000..0cdea100b72 |
| --- /dev/null |
| +++ b/lucene/licenses/log4j-api-2.11.2.jar.sha1 |
| @@ -0,0 +1 @@ |
| +f5e9a2ffca496057d6891a3de65128efc636e26e |
| diff --git a/lucene/licenses/log4j-api-LICENSE-ASL.txt b/lucene/licenses/log4j-api-LICENSE-ASL.txt |
| new file mode 100644 |
| index 00000000000..f49a4e16e68 |
| --- /dev/null |
| +++ b/lucene/licenses/log4j-api-LICENSE-ASL.txt |
| @@ -0,0 +1,201 @@ |
| + Apache License |
| + Version 2.0, January 2004 |
| + http://www.apache.org/licenses/ |
| + |
| + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
| + |
| + 1. Definitions. |
| + |
| + "License" shall mean the terms and conditions for use, reproduction, |
| + and distribution as defined by Sections 1 through 9 of this document. |
| + |
| + "Licensor" shall mean the copyright owner or entity authorized by |
| + the copyright owner that is granting the License. |
| + |
| + "Legal Entity" shall mean the union of the acting entity and all |
| + other entities that control, are controlled by, or are under common |
| + control with that entity. For the purposes of this definition, |
| + "control" means (i) the power, direct or indirect, to cause the |
| + direction or management of such entity, whether by contract or |
| + otherwise, or (ii) ownership of fifty percent (50%) or more of the |
| + outstanding shares, or (iii) beneficial ownership of such entity. |
| + |
| + "You" (or "Your") shall mean an individual or Legal Entity |
| + exercising permissions granted by this License. |
| + |
| + "Source" form shall mean the preferred form for making modifications, |
| + including but not limited to software source code, documentation |
| + source, and configuration files. |
| + |
| + "Object" form shall mean any form resulting from mechanical |
| + transformation or translation of a Source form, including but |
| + not limited to compiled object code, generated documentation, |
| + and conversions to other media types. |
| + |
| + "Work" shall mean the work of authorship, whether in Source or |
| + Object form, made available under the License, as indicated by a |
| + copyright notice that is included in or attached to the work |
| + (an example is provided in the Appendix below). |
| + |
| + "Derivative Works" shall mean any work, whether in Source or Object |
| + form, that is based on (or derived from) the Work and for which the |
| + editorial revisions, annotations, elaborations, or other modifications |
| + represent, as a whole, an original work of authorship. For the purposes |
| + of this License, Derivative Works shall not include works that remain |
| + separable from, or merely link (or bind by name) to the interfaces of, |
| + the Work and Derivative Works thereof. |
| + |
| + "Contribution" shall mean any work of authorship, including |
| + the original version of the Work and any modifications or additions |
| + to that Work or Derivative Works thereof, that is intentionally |
| + submitted to Licensor for inclusion in the Work by the copyright owner |
| + or by an individual or Legal Entity authorized to submit on behalf of |
| + the copyright owner. For the purposes of this definition, "submitted" |
| + means any form of electronic, verbal, or written communication sent |
| + to the Licensor or its representatives, including but not limited to |
| + communication on electronic mailing lists, source code control systems, |
| + and issue tracking systems that are managed by, or on behalf of, the |
| + Licensor for the purpose of discussing and improving the Work, but |
| + excluding communication that is conspicuously marked or otherwise |
| + designated in writing by the copyright owner as "Not a Contribution." |
| + |
| + "Contributor" shall mean Licensor and any individual or Legal Entity |
| + on behalf of whom a Contribution has been received by Licensor and |
| + subsequently incorporated within the Work. |
| + |
| + 2. Grant of Copyright License. Subject to the terms and conditions of |
| + this License, each Contributor hereby grants to You a perpetual, |
| + worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
| + copyright license to reproduce, prepare Derivative Works of, |
| + publicly display, publicly perform, sublicense, and distribute the |
| + Work and such Derivative Works in Source or Object form. |
| + |
| + 3. Grant of Patent License. Subject to the terms and conditions of |
| + this License, each Contributor hereby grants to You a perpetual, |
| + worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
| + (except as stated in this section) patent license to make, have made, |
| + use, offer to sell, sell, import, and otherwise transfer the Work, |
| + where such license applies only to those patent claims licensable |
| + by such Contributor that are necessarily infringed by their |
| + Contribution(s) alone or by combination of their Contribution(s) |
| + with the Work to which such Contribution(s) was submitted. If You |
| + institute patent litigation against any entity (including a |
| + cross-claim or counterclaim in a lawsuit) alleging that the Work |
| + or a Contribution incorporated within the Work constitutes direct |
| + or contributory patent infringement, then any patent licenses |
| + granted to You under this License for that Work shall terminate |
| + as of the date such litigation is filed. |
| + |
| + 4. Redistribution. You may reproduce and distribute copies of the |
| + Work or Derivative Works thereof in any medium, with or without |
| + modifications, and in Source or Object form, provided that You |
| + meet the following conditions: |
| + |
| + (a) You must give any other recipients of the Work or |
| + Derivative Works a copy of this License; and |
| + |
| + (b) You must cause any modified files to carry prominent notices |
| + stating that You changed the files; and |
| + |
| + (c) You must retain, in the Source form of any Derivative Works |
| + that You distribute, all copyright, patent, trademark, and |
| + attribution notices from the Source form of the Work, |
| + excluding those notices that do not pertain to any part of |
| + the Derivative Works; and |
| + |
| + (d) If the Work includes a "NOTICE" text file as part of its |
| + distribution, then any Derivative Works that You distribute must |
| + include a readable copy of the attribution notices contained |
| + within such NOTICE file, excluding those notices that do not |
| + pertain to any part of the Derivative Works, in at least one |
| + of the following places: within a NOTICE text file distributed |
| + as part of the Derivative Works; within the Source form or |
| + documentation, if provided along with the Derivative Works; or, |
| + within a display generated by the Derivative Works, if and |
| + wherever such third-party notices normally appear. The contents |
| + of the NOTICE file are for informational purposes only and |
| + do not modify the License. You may add Your own attribution |
| + notices within Derivative Works that You distribute, alongside |
| + or as an addendum to the NOTICE text from the Work, provided |
| + that such additional attribution notices cannot be construed |
| + as modifying the License. |
| + |
| + You may add Your own copyright statement to Your modifications and |
| + may provide additional or different license terms and conditions |
| + for use, reproduction, or distribution of Your modifications, or |
| + for any such Derivative Works as a whole, provided Your use, |
| + reproduction, and distribution of the Work otherwise complies with |
| + the conditions stated in this License. |
| + |
| + 5. Submission of Contributions. Unless You explicitly state otherwise, |
| + any Contribution intentionally submitted for inclusion in the Work |
| + by You to the Licensor shall be under the terms and conditions of |
| + this License, without any additional terms or conditions. |
| + Notwithstanding the above, nothing herein shall supersede or modify |
| + the terms of any separate license agreement you may have executed |
| + with Licensor regarding such Contributions. |
| + |
| + 6. Trademarks. This License does not grant permission to use the trade |
| + names, trademarks, service marks, or product names of the Licensor, |
| + except as required for reasonable and customary use in describing the |
| + origin of the Work and reproducing the content of the NOTICE file. |
| + |
| + 7. Disclaimer of Warranty. Unless required by applicable law or |
| + agreed to in writing, Licensor provides the Work (and each |
| + Contributor provides its Contributions) on an "AS IS" BASIS, |
| + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
| + implied, including, without limitation, any warranties or conditions |
| + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
| + PARTICULAR PURPOSE. You are solely responsible for determining the |
| + appropriateness of using or redistributing the Work and assume any |
| + risks associated with Your exercise of permissions under this License. |
| + |
| + 8. Limitation of Liability. In no event and under no legal theory, |
| + whether in tort (including negligence), contract, or otherwise, |
| + unless required by applicable law (such as deliberate and grossly |
| + negligent acts) or agreed to in writing, shall any Contributor be |
| + liable to You for damages, including any direct, indirect, special, |
| + incidental, or consequential damages of any character arising as a |
| + result of this License or out of the use or inability to use the |
| + Work (including but not limited to damages for loss of goodwill, |
| + work stoppage, computer failure or malfunction, or any and all |
| + other commercial damages or losses), even if such Contributor |
| + has been advised of the possibility of such damages. |
| + |
| + 9. Accepting Warranty or Additional Liability. While redistributing |
| + the Work or Derivative Works thereof, You may choose to offer, |
| + and charge a fee for, acceptance of support, warranty, indemnity, |
| + or other liability obligations and/or rights consistent with this |
| + License. However, in accepting such obligations, You may act only |
| + on Your own behalf and on Your sole responsibility, not on behalf |
| + of any other Contributor, and only if You agree to indemnify, |
| + defend, and hold each Contributor harmless for any liability |
| + incurred by, or claims asserted against, such Contributor by reason |
| + of your accepting any such warranty or additional liability. |
| + |
| + END OF TERMS AND CONDITIONS |
| + |
| + APPENDIX: How to apply the Apache License to your work. |
| + |
| + To apply the Apache License to your work, attach the following |
| + boilerplate notice, with the fields enclosed by brackets "[]" |
| + replaced with your own identifying information. (Don't include |
| + the brackets!) The text should be enclosed in the appropriate |
| + comment syntax for the file format. We also recommend that a |
| + file or class name and description of purpose be included on the |
| + same "printed page" as the copyright notice for easier |
| + identification within third-party archives. |
| + |
| + Copyright [yyyy] [name of copyright owner] |
| + |
| + Licensed under the Apache License, Version 2.0 (the "License"); |
| + you may not use this file except in compliance with the License. |
| + You may obtain a copy of the License at |
| + |
| + http://www.apache.org/licenses/LICENSE-2.0 |
| + |
| + Unless required by applicable law or agreed to in writing, software |
| + distributed under the License is distributed on an "AS IS" BASIS, |
| + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + See the License for the specific language governing permissions and |
| + limitations under the License. |
| \ No newline at end of file |
| diff --git a/lucene/licenses/log4j-api-NOTICE.txt b/lucene/licenses/log4j-api-NOTICE.txt |
| new file mode 100644 |
| index 00000000000..ebba5ac0018 |
| --- /dev/null |
| +++ b/lucene/licenses/log4j-api-NOTICE.txt |
| @@ -0,0 +1,17 @@ |
| +Apache Log4j |
| +Copyright 1999-2017 Apache Software Foundation |
| + |
| +This product includes software developed at |
| +The Apache Software Foundation (http://www.apache.org/). |
| + |
| +ResolverUtil.java |
| +Copyright 2005-2006 Tim Fennell |
| + |
| +Dumbster SMTP test server |
| +Copyright 2004 Jason Paul Kitchen |
| + |
| +TypeUtil.java |
| +Copyright 2002-2012 Ramnivas Laddad, Juergen Hoeller, Chris Beams |
| + |
| +picocli (http://picocli.info) |
| +Copyright 2017 Remko Popma |
| \ No newline at end of file |
| diff --git a/lucene/licenses/log4j-core-2.11.2.jar.sha1 b/lucene/licenses/log4j-core-2.11.2.jar.sha1 |
| new file mode 100644 |
| index 00000000000..ec2acae4df7 |
| --- /dev/null |
| +++ b/lucene/licenses/log4j-core-2.11.2.jar.sha1 |
| @@ -0,0 +1 @@ |
| +6c2fb3f5b7cd27504726aef1b674b542a0c9cf53 |
| diff --git a/lucene/licenses/log4j-core-LICENSE-ASL.txt b/lucene/licenses/log4j-core-LICENSE-ASL.txt |
| new file mode 100644 |
| index 00000000000..f49a4e16e68 |
| --- /dev/null |
| +++ b/lucene/licenses/log4j-core-LICENSE-ASL.txt |
| @@ -0,0 +1,201 @@ |
| + Apache License |
| + Version 2.0, January 2004 |
| + http://www.apache.org/licenses/ |
| + |
| + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
| + |
| + 1. Definitions. |
| + |
| + "License" shall mean the terms and conditions for use, reproduction, |
| + and distribution as defined by Sections 1 through 9 of this document. |
| + |
| + "Licensor" shall mean the copyright owner or entity authorized by |
| + the copyright owner that is granting the License. |
| + |
| + "Legal Entity" shall mean the union of the acting entity and all |
| + other entities that control, are controlled by, or are under common |
| + control with that entity. For the purposes of this definition, |
| + "control" means (i) the power, direct or indirect, to cause the |
| + direction or management of such entity, whether by contract or |
| + otherwise, or (ii) ownership of fifty percent (50%) or more of the |
| + outstanding shares, or (iii) beneficial ownership of such entity. |
| + |
| + "You" (or "Your") shall mean an individual or Legal Entity |
| + exercising permissions granted by this License. |
| + |
| + "Source" form shall mean the preferred form for making modifications, |
| + including but not limited to software source code, documentation |
| + source, and configuration files. |
| + |
| + "Object" form shall mean any form resulting from mechanical |
| + transformation or translation of a Source form, including but |
| + not limited to compiled object code, generated documentation, |
| + and conversions to other media types. |
| + |
| + "Work" shall mean the work of authorship, whether in Source or |
| + Object form, made available under the License, as indicated by a |
| + copyright notice that is included in or attached to the work |
| + (an example is provided in the Appendix below). |
| + |
| + "Derivative Works" shall mean any work, whether in Source or Object |
| + form, that is based on (or derived from) the Work and for which the |
| + editorial revisions, annotations, elaborations, or other modifications |
| + represent, as a whole, an original work of authorship. For the purposes |
| + of this License, Derivative Works shall not include works that remain |
| + separable from, or merely link (or bind by name) to the interfaces of, |
| + the Work and Derivative Works thereof. |
| + |
| + "Contribution" shall mean any work of authorship, including |
| + the original version of the Work and any modifications or additions |
| + to that Work or Derivative Works thereof, that is intentionally |
| + submitted to Licensor for inclusion in the Work by the copyright owner |
| + or by an individual or Legal Entity authorized to submit on behalf of |
| + the copyright owner. For the purposes of this definition, "submitted" |
| + means any form of electronic, verbal, or written communication sent |
| + to the Licensor or its representatives, including but not limited to |
| + communication on electronic mailing lists, source code control systems, |
| + and issue tracking systems that are managed by, or on behalf of, the |
| + Licensor for the purpose of discussing and improving the Work, but |
| + excluding communication that is conspicuously marked or otherwise |
| + designated in writing by the copyright owner as "Not a Contribution." |
| + |
| + "Contributor" shall mean Licensor and any individual or Legal Entity |
| + on behalf of whom a Contribution has been received by Licensor and |
| + subsequently incorporated within the Work. |
| + |
| + 2. Grant of Copyright License. Subject to the terms and conditions of |
| + this License, each Contributor hereby grants to You a perpetual, |
| + worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
| + copyright license to reproduce, prepare Derivative Works of, |
| + publicly display, publicly perform, sublicense, and distribute the |
| + Work and such Derivative Works in Source or Object form. |
| + |
| + 3. Grant of Patent License. Subject to the terms and conditions of |
| + this License, each Contributor hereby grants to You a perpetual, |
| + worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
| + (except as stated in this section) patent license to make, have made, |
| + use, offer to sell, sell, import, and otherwise transfer the Work, |
| + where such license applies only to those patent claims licensable |
| + by such Contributor that are necessarily infringed by their |
| + Contribution(s) alone or by combination of their Contribution(s) |
| + with the Work to which such Contribution(s) was submitted. If You |
| + institute patent litigation against any entity (including a |
| + cross-claim or counterclaim in a lawsuit) alleging that the Work |
| + or a Contribution incorporated within the Work constitutes direct |
| + or contributory patent infringement, then any patent licenses |
| + granted to You under this License for that Work shall terminate |
| + as of the date such litigation is filed. |
| + |
| + 4. Redistribution. You may reproduce and distribute copies of the |
| + Work or Derivative Works thereof in any medium, with or without |
| + modifications, and in Source or Object form, provided that You |
| + meet the following conditions: |
| + |
| + (a) You must give any other recipients of the Work or |
| + Derivative Works a copy of this License; and |
| + |
| + (b) You must cause any modified files to carry prominent notices |
| + stating that You changed the files; and |
| + |
| + (c) You must retain, in the Source form of any Derivative Works |
| + that You distribute, all copyright, patent, trademark, and |
| + attribution notices from the Source form of the Work, |
| + excluding those notices that do not pertain to any part of |
| + the Derivative Works; and |
| + |
| + (d) If the Work includes a "NOTICE" text file as part of its |
| + distribution, then any Derivative Works that You distribute must |
| + include a readable copy of the attribution notices contained |
| + within such NOTICE file, excluding those notices that do not |
| + pertain to any part of the Derivative Works, in at least one |
| + of the following places: within a NOTICE text file distributed |
| + as part of the Derivative Works; within the Source form or |
| + documentation, if provided along with the Derivative Works; or, |
| + within a display generated by the Derivative Works, if and |
| + wherever such third-party notices normally appear. The contents |
| + of the NOTICE file are for informational purposes only and |
| + do not modify the License. You may add Your own attribution |
| + notices within Derivative Works that You distribute, alongside |
| + or as an addendum to the NOTICE text from the Work, provided |
| + that such additional attribution notices cannot be construed |
| + as modifying the License. |
| + |
| + You may add Your own copyright statement to Your modifications and |
| + may provide additional or different license terms and conditions |
| + for use, reproduction, or distribution of Your modifications, or |
| + for any such Derivative Works as a whole, provided Your use, |
| + reproduction, and distribution of the Work otherwise complies with |
| + the conditions stated in this License. |
| + |
| + 5. Submission of Contributions. Unless You explicitly state otherwise, |
| + any Contribution intentionally submitted for inclusion in the Work |
| + by You to the Licensor shall be under the terms and conditions of |
| + this License, without any additional terms or conditions. |
| + Notwithstanding the above, nothing herein shall supersede or modify |
| + the terms of any separate license agreement you may have executed |
| + with Licensor regarding such Contributions. |
| + |
| + 6. Trademarks. This License does not grant permission to use the trade |
| + names, trademarks, service marks, or product names of the Licensor, |
| + except as required for reasonable and customary use in describing the |
| + origin of the Work and reproducing the content of the NOTICE file. |
| + |
| + 7. Disclaimer of Warranty. Unless required by applicable law or |
| + agreed to in writing, Licensor provides the Work (and each |
| + Contributor provides its Contributions) on an "AS IS" BASIS, |
| + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
| + implied, including, without limitation, any warranties or conditions |
| + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
| + PARTICULAR PURPOSE. You are solely responsible for determining the |
| + appropriateness of using or redistributing the Work and assume any |
| + risks associated with Your exercise of permissions under this License. |
| + |
| + 8. Limitation of Liability. In no event and under no legal theory, |
| + whether in tort (including negligence), contract, or otherwise, |
| + unless required by applicable law (such as deliberate and grossly |
| + negligent acts) or agreed to in writing, shall any Contributor be |
| + liable to You for damages, including any direct, indirect, special, |
| + incidental, or consequential damages of any character arising as a |
| + result of this License or out of the use or inability to use the |
| + Work (including but not limited to damages for loss of goodwill, |
| + work stoppage, computer failure or malfunction, or any and all |
| + other commercial damages or losses), even if such Contributor |
| + has been advised of the possibility of such damages. |
| + |
| + 9. Accepting Warranty or Additional Liability. While redistributing |
| + the Work or Derivative Works thereof, You may choose to offer, |
| + and charge a fee for, acceptance of support, warranty, indemnity, |
| + or other liability obligations and/or rights consistent with this |
| + License. However, in accepting such obligations, You may act only |
| + on Your own behalf and on Your sole responsibility, not on behalf |
| + of any other Contributor, and only if You agree to indemnify, |
| + defend, and hold each Contributor harmless for any liability |
| + incurred by, or claims asserted against, such Contributor by reason |
| + of your accepting any such warranty or additional liability. |
| + |
| + END OF TERMS AND CONDITIONS |
| + |
| + APPENDIX: How to apply the Apache License to your work. |
| + |
| + To apply the Apache License to your work, attach the following |
| + boilerplate notice, with the fields enclosed by brackets "[]" |
| + replaced with your own identifying information. (Don't include |
| + the brackets!) The text should be enclosed in the appropriate |
| + comment syntax for the file format. We also recommend that a |
| + file or class name and description of purpose be included on the |
| + same "printed page" as the copyright notice for easier |
| + identification within third-party archives. |
| + |
| + Copyright [yyyy] [name of copyright owner] |
| + |
| + Licensed under the Apache License, Version 2.0 (the "License"); |
| + you may not use this file except in compliance with the License. |
| + You may obtain a copy of the License at |
| + |
| + http://www.apache.org/licenses/LICENSE-2.0 |
| + |
| + Unless required by applicable law or agreed to in writing, software |
| + distributed under the License is distributed on an "AS IS" BASIS, |
| + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + See the License for the specific language governing permissions and |
| + limitations under the License. |
| \ No newline at end of file |
| diff --git a/lucene/licenses/log4j-core-NOTICE.txt b/lucene/licenses/log4j-core-NOTICE.txt |
| new file mode 100644 |
| index 00000000000..ebba5ac0018 |
| --- /dev/null |
| +++ b/lucene/licenses/log4j-core-NOTICE.txt |
| @@ -0,0 +1,17 @@ |
| +Apache Log4j |
| +Copyright 1999-2017 Apache Software Foundation |
| + |
| +This product includes software developed at |
| +The Apache Software Foundation (http://www.apache.org/). |
| + |
| +ResolverUtil.java |
| +Copyright 2005-2006 Tim Fennell |
| + |
| +Dumbster SMTP test server |
| +Copyright 2004 Jason Paul Kitchen |
| + |
| +TypeUtil.java |
| +Copyright 2002-2012 Ramnivas Laddad, Juergen Hoeller, Chris Beams |
| + |
| +picocli (http://picocli.info) |
| +Copyright 2017 Remko Popma |
| \ No newline at end of file |
| diff --git a/lucene/luke/bin/luke.bat b/lucene/luke/bin/luke.bat |
| new file mode 100644 |
| index 00000000000..4d83d8bf319 |
| --- /dev/null |
| +++ b/lucene/luke/bin/luke.bat |
| @@ -0,0 +1,13 @@ |
| +@echo off |
| +@setlocal enabledelayedexpansion |
| + |
| +cd /d %~dp0 |
| + |
| +set JAVA_OPTIONS=%JAVA_OPTIONS% -Xmx1024m -Xms512m -XX:MaxMetaspaceSize=256m |
| + |
| +set CLASSPATHS=.\*;.\lib\*;..\core\*;..\codecs\*;..\backward-codecs\*;..\queries\*;..\queryparser\*;..\suggest\*;..\misc\* |
| +for /d %%A in (..\analysis\*) do ( |
| + set "CLASSPATHS=!CLASSPATHS!;%%A\*;%%A\lib\*" |
| +) |
| + |
| +start javaw -cp %CLASSPATHS% %JAVA_OPTIONS% org.apache.lucene.luke.app.desktop.LukeMain |
| diff --git a/lucene/luke/bin/luke.sh b/lucene/luke/bin/luke.sh |
| new file mode 100755 |
| index 00000000000..7c7d9191056 |
| --- /dev/null |
| +++ b/lucene/luke/bin/luke.sh |
| @@ -0,0 +1,18 @@ |
| +#!/bin/bash |
| + |
| +LUKE_HOME=$(cd $(dirname $0) && pwd) |
| +cd ${LUKE_HOME} |
| + |
| +JAVA_OPTIONS="${JAVA_OPTIONS} -Xmx1024m -Xms512m -XX:MaxMetaspaceSize=256m" |
| + |
| +CLASSPATHS="./*:./lib/*:../core/*:../codecs/*:../backward-codecs/*:../queries/*:../queryparser/*:../suggest/*:../misc/*" |
| +for dir in `ls ../analysis`; do |
| + CLASSPATHS="${CLASSPATHS}:../analysis/${dir}/*:../analysis/${dir}/lib/*" |
| +done |
| + |
| +LOG_DIR=${HOME}/.luke.d/ |
| + if [[ ! -d ${LOG_DIR} ]]; then |
| + mkdir ${LOG_DIR} |
| + fi |
| + |
| +nohup java -cp ${CLASSPATHS} ${JAVA_OPTIONS} org.apache.lucene.luke.app.desktop.LukeMain > ${LOG_DIR}/luke_out.log 2>&1 & |
| diff --git a/lucene/luke/build.xml b/lucene/luke/build.xml |
| new file mode 100644 |
| index 00000000000..9064d26e488 |
| --- /dev/null |
| +++ b/lucene/luke/build.xml |
| @@ -0,0 +1,77 @@ |
| +<?xml version="1.0"?> |
| + |
| +<!-- |
| + Licensed to the Apache Software Foundation (ASF) under one or more |
| + contributor license agreements. See the NOTICE file distributed with |
| + this work for additional information regarding copyright ownership. |
| + The ASF licenses this file to You under the Apache License, Version 2.0 |
| + the "License"); you may not use this file except in compliance with |
| + the License. You may obtain a copy of the License at |
| + |
| + http://www.apache.org/licenses/LICENSE-2.0 |
| + |
| + Unless required by applicable law or agreed to in writing, software |
| + distributed under the License is distributed on an "AS IS" BASIS, |
| + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + See the License for the specific language governing permissions and |
| + limitations under the License. |
| + --> |
| + |
| +<project name="luke" default="default"> |
| + |
| + <description> |
| + Luke - Lucene Toolbox |
| + </description> |
| + |
| + <!-- use full Java SE API (project default 'compact2' does not include Swing) --> |
| + <property name="javac.profile.args" value=""/> |
| + |
| + <import file="../module-build.xml"/> |
| + |
| + <target name="init" depends="module-build.init,jar-lucene-core"/> |
| + |
| + <path id="classpath"> |
| + <pathelement path="${lucene-core.jar}"/> |
| + <pathelement path="${codecs.jar}"/> |
| + <pathelement path="${backward-codecs.jar}"/> |
| + <pathelement path="${analyzers-common.jar}"/> |
| + <pathelement path="${misc.jar}"/> |
| + <pathelement path="${queryparser.jar}"/> |
| + <pathelement path="${queries.jar}"/> |
| + <fileset dir="lib"/> |
| + <path refid="base.classpath"/> |
| + </path> |
| + |
| + <target name="javadocs" depends="compile-core,javadocs-lucene-core,javadocs-analyzers-common,check-javadocs-uptodate" |
| + unless="javadocs-uptodate-${name}"> |
| + <invoke-module-javadoc> |
| + <links> |
| + <link href="../analyzers-common"/> |
| + </links> |
| + </invoke-module-javadoc> |
| + </target> |
| + |
| + <target name="build-artifacts-and-tests" depends="jar, compile-test"> |
| + <!-- copy start scripts --> |
| + <copy todir="${build.dir}"> |
| + <fileset dir="${common.dir}/luke/bin"> |
| + <include name="**/*.sh"/> |
| + <include name="**/*.bat"/> |
| + </fileset> |
| + </copy> |
| + </target> |
| + |
| + <!-- launch Luke --> |
| + <target name="run" depends="compile-core" description="Launch Luke GUI"> |
| + <java classname="org.apache.lucene.luke.app.desktop.LukeMain" |
| + classpath="${build.dir}/classes/java" |
| + fork="true" |
| + maxmemory="512m"> |
| + <classpath refid="classpath"/> |
| + </java> |
| + </target> |
| + |
| + <target name="compile-core" |
| + depends="jar-codecs,jar-backward-codecs,jar-analyzers-common,jar-misc,jar-queryparser,jar-queries,jar-misc,common.compile-core"/> |
| + |
| +</project> |
| diff --git a/lucene/luke/ivy.xml b/lucene/luke/ivy.xml |
| new file mode 100644 |
| index 00000000000..88d9d8c63b6 |
| --- /dev/null |
| +++ b/lucene/luke/ivy.xml |
| @@ -0,0 +1,34 @@ |
| +<!-- |
| + Licensed to the Apache Software Foundation (ASF) under one |
| + or more contributor license agreements. See the NOTICE file |
| + distributed with this work for additional information |
| + regarding copyright ownership. The ASF licenses this file |
| + to you under the Apache License, Version 2.0 (the |
| + "License"); you may not use this file except in compliance |
| + with the License. You may obtain a copy of the License at |
| + |
| + http://www.apache.org/licenses/LICENSE-2.0 |
| + |
| + Unless required by applicable law or agreed to in writing, |
| + software distributed under the License is distributed on an |
| + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| + KIND, either express or implied. See the License for the |
| + specific language governing permissions and limitations |
| + under the License. |
| +--> |
| +<ivy-module version="2.0"> |
| + <info organisation="org.apache.lucene" module="luke"/> |
| + |
| + <configurations defaultconfmapping="compile->default;logging->default"> |
| + <conf name="compile" transitive="false"/> |
| + <conf name="logging" transitive="false"/> |
| + </configurations> |
| + |
| + <dependencies> |
| + <dependency org="org.apache.logging.log4j" name="log4j-api" rev="${/org.apache.logging.log4j/log4j-api}" |
| + conf="logging"/> |
| + <dependency org="org.apache.logging.log4j" name="log4j-core" rev="${/org.apache.logging.log4j/log4j-core}" |
| + conf="logging"/> |
| + <exclude org="*" ext="*" matcher="regexp" type="${ivy.exclude.types}"/> |
| + </dependencies> |
| +</ivy-module> |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/AbstractHandler.java b/lucene/luke/src/java/org/apache/lucene/luke/app/AbstractHandler.java |
| new file mode 100644 |
| index 00000000000..ab967a8d149 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/AbstractHandler.java |
| @@ -0,0 +1,47 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app; |
| + |
| +import java.lang.invoke.MethodHandles; |
| +import java.util.ArrayList; |
| +import java.util.List; |
| + |
| +import org.apache.logging.log4j.Logger; |
| +import org.apache.lucene.luke.util.LoggerFactory; |
| + |
| +/** Abstract handler class */ |
| +public abstract class AbstractHandler<T extends Observer> { |
| + |
| + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| + |
| + private List<T> observers = new ArrayList<>(); |
| + |
| + public void addObserver(T observer) { |
| + observers.add(observer); |
| + log.debug("{} registered.", observer.getClass().getName()); |
| + } |
| + |
| + void notifyObservers() { |
| + for (T observer : observers) { |
| + notifyOne(observer); |
| + } |
| + } |
| + |
| + protected abstract void notifyOne(T observer); |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/DirectoryHandler.java b/lucene/luke/src/java/org/apache/lucene/luke/app/DirectoryHandler.java |
| new file mode 100644 |
| index 00000000000..ec4e7e5d23a |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/DirectoryHandler.java |
| @@ -0,0 +1,112 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app; |
| + |
| +import java.io.IOException; |
| +import java.util.Objects; |
| + |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.models.LukeException; |
| +import org.apache.lucene.luke.models.util.IndexUtils; |
| +import org.apache.lucene.store.Directory; |
| + |
| +/** Directory open/close handler */ |
| +public final class DirectoryHandler extends AbstractHandler<DirectoryObserver> { |
| + |
| + private static final DirectoryHandler instance = new DirectoryHandler(); |
| + |
| + private LukeStateImpl state; |
| + |
| + public static DirectoryHandler getInstance() { |
| + return instance; |
| + } |
| + |
| + @Override |
| + protected void notifyOne(DirectoryObserver observer) { |
| + if (state.closed) { |
| + observer.closeDirectory(); |
| + } else { |
| + observer.openDirectory(state); |
| + } |
| + } |
| + |
| + public boolean directoryOpened() { |
| + return state != null && !state.closed; |
| + } |
| + |
| + public void open(String indexPath, String dirImpl) { |
| + Objects.requireNonNull(indexPath); |
| + |
| + if (directoryOpened()) { |
| + close(); |
| + } |
| + |
| + Directory dir; |
| + try { |
| + dir = IndexUtils.openDirectory(indexPath, dirImpl); |
| + } catch (IOException e) { |
| + throw new LukeException(MessageUtils.getLocalizedMessage("openindex.message.index_path_invalid", indexPath), e); |
| + } |
| + |
| + state = new LukeStateImpl(); |
| + state.indexPath = indexPath; |
| + state.dirImpl = dirImpl; |
| + state.dir = dir; |
| + |
| + notifyObservers(); |
| + } |
| + |
| + public void close() { |
| + if (state == null) { |
| + return; |
| + } |
| + |
| + IndexUtils.close(state.dir); |
| + |
| + state.closed = true; |
| + notifyObservers(); |
| + } |
| + |
| + public LukeState getState() { |
| + return state; |
| + } |
| + |
| + private static class LukeStateImpl implements LukeState { |
| + private boolean closed = false; |
| + |
| + private String indexPath; |
| + private String dirImpl; |
| + private Directory dir; |
| + |
| + @Override |
| + public String getIndexPath() { |
| + return indexPath; |
| + } |
| + |
| + @Override |
| + public String getDirImpl() { |
| + return dirImpl; |
| + } |
| + |
| + @Override |
| + public Directory getDirectory() { |
| + return dir; |
| + } |
| + } |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/DirectoryObserver.java b/lucene/luke/src/java/org/apache/lucene/luke/app/DirectoryObserver.java |
| new file mode 100644 |
| index 00000000000..64371150f87 |
| --- /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 00000000000..17e407043e1 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/IndexHandler.java |
| @@ -0,0 +1,147 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app; |
| + |
| +import java.lang.invoke.MethodHandles; |
| +import java.util.Objects; |
| + |
| +import org.apache.logging.log4j.Logger; |
| +import org.apache.lucene.index.IndexReader; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.models.LukeException; |
| +import org.apache.lucene.luke.models.util.IndexUtils; |
| +import org.apache.lucene.luke.util.LoggerFactory; |
| + |
| +/** Index open/close handler */ |
| +public final class IndexHandler extends AbstractHandler<IndexObserver> { |
| + |
| + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| + |
| + private static final IndexHandler instance = new IndexHandler(); |
| + |
| + private LukeStateImpl state; |
| + |
| + public static IndexHandler getInstance() { |
| + return instance; |
| + } |
| + |
| + @Override |
| + protected void notifyOne(IndexObserver observer) { |
| + if (state.closed) { |
| + observer.closeIndex(); |
| + } else { |
| + observer.openIndex(state); |
| + } |
| + } |
| + |
| + public boolean indexOpened() { |
| + return state != null && !state.closed; |
| + } |
| + |
| + public void open(String indexPath, String dirImpl) { |
| + open(indexPath, dirImpl, false, false, false); |
| + } |
| + |
| + public void open(String indexPath, String dirImpl, boolean readOnly, boolean useCompound, boolean keepAllCommits) { |
| + Objects.requireNonNull(indexPath); |
| + |
| + if (indexOpened()) { |
| + close(); |
| + } |
| + |
| + IndexReader reader; |
| + try { |
| + reader = IndexUtils.openIndex(indexPath, dirImpl); |
| + } catch (Exception e) { |
| + log.error(e.getMessage(), e); |
| + throw new LukeException(MessageUtils.getLocalizedMessage("openindex.message.index_path_invalid", indexPath), e); |
| + } |
| + |
| + state = new LukeStateImpl(); |
| + state.indexPath = indexPath; |
| + state.reader = reader; |
| + state.dirImpl = dirImpl; |
| + state.readOnly = readOnly; |
| + state.useCompound = useCompound; |
| + state.keepAllCommits = keepAllCommits; |
| + |
| + notifyObservers(); |
| + } |
| + |
| + public void close() { |
| + if (state == null) { |
| + return; |
| + } |
| + |
| + IndexUtils.close(state.reader); |
| + |
| + state.closed = true; |
| + notifyObservers(); |
| + } |
| + |
| + public void reOpen() { |
| + close(); |
| + open(state.getIndexPath(), state.getDirImpl(), state.readOnly(), state.useCompound(), state.keepAllCommits()); |
| + } |
| + |
| + public LukeState getState() { |
| + return state; |
| + } |
| + |
| + private static class LukeStateImpl implements LukeState { |
| + |
| + private boolean closed = false; |
| + |
| + private String indexPath; |
| + private IndexReader reader; |
| + private String dirImpl; |
| + private boolean readOnly; |
| + private boolean useCompound; |
| + private boolean keepAllCommits; |
| + |
| + @Override |
| + public String getIndexPath() { |
| + return indexPath; |
| + } |
| + |
| + @Override |
| + public IndexReader getIndexReader() { |
| + return reader; |
| + } |
| + |
| + @Override |
| + public String getDirImpl() { |
| + return dirImpl; |
| + } |
| + |
| + @Override |
| + public boolean readOnly() { |
| + return readOnly; |
| + } |
| + |
| + @Override |
| + public boolean useCompound() { |
| + return useCompound; |
| + } |
| + |
| + @Override |
| + public boolean keepAllCommits() { |
| + return keepAllCommits; |
| + } |
| + } |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/IndexObserver.java b/lucene/luke/src/java/org/apache/lucene/luke/app/IndexObserver.java |
| new file mode 100644 |
| index 00000000000..599b1090c4d |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/IndexObserver.java |
| @@ -0,0 +1,27 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app; |
| + |
| +/** Index open/close observer */ |
| +public interface IndexObserver extends Observer { |
| + |
| + void openIndex(LukeState state); |
| + |
| + void closeIndex(); |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/LukeState.java b/lucene/luke/src/java/org/apache/lucene/luke/app/LukeState.java |
| new file mode 100644 |
| index 00000000000..33ca829bca5 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/LukeState.java |
| @@ -0,0 +1,57 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app; |
| + |
| +import org.apache.lucene.index.DirectoryReader; |
| +import org.apache.lucene.index.IndexReader; |
| +import org.apache.lucene.store.Directory; |
| + |
| +/** |
| + * Holder for current index/directory. |
| + */ |
| +public interface LukeState { |
| + |
| + String getIndexPath(); |
| + |
| + String getDirImpl(); |
| + |
| + default Directory getDirectory() { |
| + throw new UnsupportedOperationException(); |
| + } |
| + |
| + default IndexReader getIndexReader() { |
| + throw new UnsupportedOperationException(); |
| + } |
| + |
| + default boolean readOnly() { |
| + throw new UnsupportedOperationException(); |
| + } |
| + |
| + default boolean useCompound() { |
| + throw new UnsupportedOperationException(); |
| + } |
| + |
| + default boolean keepAllCommits() { |
| + throw new UnsupportedOperationException(); |
| + } |
| + |
| + default boolean hasDirectoryReader() { |
| + return getIndexReader() instanceof DirectoryReader; |
| + } |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/Observer.java b/lucene/luke/src/java/org/apache/lucene/luke/app/Observer.java |
| new file mode 100644 |
| index 00000000000..290865b8986 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/Observer.java |
| @@ -0,0 +1,22 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app; |
| + |
| +/** Marker interface for observers */ |
| +public interface Observer { |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/LukeMain.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/LukeMain.java |
| new file mode 100644 |
| index 00000000000..fae52f29abd |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/LukeMain.java |
| @@ -0,0 +1,94 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop; |
| + |
| +import javax.swing.JFrame; |
| +import javax.swing.UIManager; |
| +import java.awt.GraphicsEnvironment; |
| +import java.io.IOException; |
| +import java.lang.invoke.MethodHandles; |
| +import java.nio.file.FileSystems; |
| + |
| +import org.apache.logging.log4j.Logger; |
| +import org.apache.lucene.luke.app.desktop.components.LukeWindowProvider; |
| +import org.apache.lucene.luke.app.desktop.components.dialog.menubar.OpenIndexDialogFactory; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.FontUtils; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.util.LoggerFactory; |
| + |
| +import static org.apache.lucene.luke.app.desktop.util.ExceptionHandler.handle; |
| + |
| +/** Entry class for desktop Luke */ |
| +public class LukeMain { |
| + |
| + public static final String LOG_FILE = System.getProperty("user.home") + |
| + FileSystems.getDefault().getSeparator() + ".luke.d" + |
| + FileSystems.getDefault().getSeparator() + "luke.log"; |
| + |
| + static { |
| + LoggerFactory.initGuiLogging(LOG_FILE); |
| + } |
| + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| + |
| + private static JFrame frame; |
| + |
| + public static JFrame getOwnerFrame() { |
| + return frame; |
| + } |
| + |
| + private static void createAndShowGUI() { |
| + // uncaught error handler |
| + MessageBroker messageBroker = MessageBroker.getInstance(); |
| + Thread.setDefaultUncaughtExceptionHandler((thread, cause) -> |
| + handle(cause, messageBroker) |
| + ); |
| + |
| + try { |
| + frame = new LukeWindowProvider().get(); |
| + frame.setLocation(200, 100); |
| + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); |
| + frame.pack(); |
| + frame.setVisible(true); |
| + |
| + // show open index dialog |
| + OpenIndexDialogFactory openIndexDialogFactory = OpenIndexDialogFactory.getInstance(); |
| + new DialogOpener<>(openIndexDialogFactory).open(MessageUtils.getLocalizedMessage("openindex.dialog.title"), 600, 420, |
| + (factory) -> { |
| + }); |
| + } catch (IOException e) { |
| + messageBroker.showUnknownErrorMessage(); |
| + log.error("Cannot initialize components.", e); |
| + } |
| + } |
| + |
| + public static void main(String[] args) throws Exception { |
| + String lookAndFeelClassName = UIManager.getSystemLookAndFeelClassName(); |
| + if (!lookAndFeelClassName.contains("AquaLookAndFeel") && !lookAndFeelClassName.contains("PlasticXPLookAndFeel")) { |
| + // may be running on linux platform |
| + lookAndFeelClassName = "javax.swing.plaf.metal.MetalLookAndFeel"; |
| + } |
| + UIManager.setLookAndFeel(lookAndFeelClassName); |
| + |
| + GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment(); |
| + genv.registerFont(FontUtils.createElegantIconFont()); |
| + |
| + javax.swing.SwingUtilities.invokeLater(LukeMain::createAndShowGUI); |
| + |
| + } |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/MessageBroker.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/MessageBroker.java |
| new file mode 100644 |
| index 00000000000..9609a2f56ef |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/MessageBroker.java |
| @@ -0,0 +1,65 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop; |
| + |
| +import java.util.ArrayList; |
| +import java.util.List; |
| + |
| +/** Message broker */ |
| +public class MessageBroker { |
| + |
| + private static final MessageBroker instance = new MessageBroker(); |
| + |
| + private List<MessageReceiver> receivers = new ArrayList<>(); |
| + |
| + public static MessageBroker getInstance() { |
| + return instance; |
| + } |
| + |
| + public void registerReceiver(MessageReceiver receiver) { |
| + receivers.add(receiver); |
| + } |
| + |
| + public void showStatusMessage(String message) { |
| + for (MessageReceiver receiver : receivers) { |
| + receiver.showStatusMessage(message); |
| + } |
| + } |
| + |
| + public void showUnknownErrorMessage() { |
| + for (MessageReceiver receiver : receivers) { |
| + receiver.showUnknownErrorMessage(); |
| + } |
| + } |
| + |
| + public void clearStatusMessage() { |
| + for (MessageReceiver receiver : receivers) { |
| + receiver.clearStatusMessage(); |
| + } |
| + } |
| + |
| + /** Message receiver in charge of rendering the message. */ |
| + public interface MessageReceiver { |
| + void showStatusMessage(String message); |
| + |
| + void showUnknownErrorMessage(); |
| + |
| + void clearStatusMessage(); |
| + } |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/Preferences.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/Preferences.java |
| new file mode 100644 |
| index 00000000000..b0df6607403 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/Preferences.java |
| @@ -0,0 +1,69 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop; |
| + |
| +import java.awt.Color; |
| +import java.io.IOException; |
| +import java.util.List; |
| + |
| +/** Preference */ |
| +public interface Preferences { |
| + |
| + List<String> getHistory(); |
| + |
| + void addHistory(String indexPath) throws IOException; |
| + |
| + boolean isReadOnly(); |
| + |
| + String getDirImpl(); |
| + |
| + boolean isNoReader(); |
| + |
| + boolean isUseCompound(); |
| + |
| + boolean isKeepAllCommits(); |
| + |
| + void setIndexOpenerPrefs(boolean readOnly, String dirImpl, boolean noReader, boolean useCompound, boolean keepAllCommits) throws IOException; |
| + |
| + ColorTheme getColorTheme(); |
| + |
| + void setColorTheme(ColorTheme theme) throws IOException; |
| + |
| + /** color themes */ |
| + enum ColorTheme { |
| + |
| + /* Gray theme */ |
| + GRAY(Color.decode("#e6e6e6")), |
| + /* Classic theme */ |
| + CLASSIC(Color.decode("#ece9d0")), |
| + /* Sandstone theme */ |
| + SANDSTONE(Color.decode("#ddd9d4")), |
| + /* Navy theme */ |
| + NAVY(Color.decode("#e6e6ff")); |
| + |
| + private Color backgroundColor; |
| + |
| + ColorTheme(Color backgroundColor) { |
| + this.backgroundColor = backgroundColor; |
| + } |
| + |
| + public Color getBackgroundColor() { |
| + return backgroundColor; |
| + } |
| + } |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/PreferencesFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/PreferencesFactory.java |
| new file mode 100644 |
| index 00000000000..2502553297f |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/PreferencesFactory.java |
| @@ -0,0 +1,34 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop; |
| + |
| +import java.io.IOException; |
| + |
| +/** Factory of {@link Preferences} */ |
| +public class PreferencesFactory { |
| + |
| + private static Preferences prefs; |
| + |
| + public synchronized static Preferences getInstance() throws IOException { |
| + if (prefs == null) { |
| + prefs = new PreferencesImpl(); |
| + } |
| + return prefs; |
| + } |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/PreferencesImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/PreferencesImpl.java |
| new file mode 100644 |
| index 00000000000..ebf78c5a57b |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/PreferencesImpl.java |
| @@ -0,0 +1,143 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop; |
| + |
| +import java.io.IOException; |
| +import java.nio.file.FileSystems; |
| +import java.nio.file.Files; |
| +import java.nio.file.Path; |
| +import java.util.ArrayList; |
| +import java.util.List; |
| + |
| +import org.apache.lucene.luke.app.desktop.util.inifile.IniFile; |
| +import org.apache.lucene.luke.app.desktop.util.inifile.SimpleIniFile; |
| +import org.apache.lucene.store.FSDirectory; |
| + |
| +/** Default implementation of {@link Preferences} */ |
| +public final class PreferencesImpl implements Preferences { |
| + |
| + private static final String CONFIG_DIR = System.getProperty("user.home") + FileSystems.getDefault().getSeparator() + ".luke.d"; |
| + private static final String INIT_FILE = "luke.ini"; |
| + private static final String HISTORY_FILE = "history"; |
| + private static final int MAX_HISTORY = 10; |
| + |
| + private final IniFile ini = new SimpleIniFile(); |
| + |
| + |
| + private final List<String> history = new ArrayList<>(); |
| + |
| + public PreferencesImpl() throws IOException { |
| + // create config dir if not exists |
| + Path confDir = FileSystems.getDefault().getPath(CONFIG_DIR); |
| + if (!Files.exists(confDir)) { |
| + Files.createDirectory(confDir); |
| + } |
| + |
| + // load configs |
| + if (Files.exists(iniFile())) { |
| + ini.load(iniFile()); |
| + } else { |
| + ini.store(iniFile()); |
| + } |
| + |
| + // load history |
| + Path histFile = historyFile(); |
| + if (Files.exists(histFile)) { |
| + List<String> allHistory = Files.readAllLines(histFile); |
| + history.addAll(allHistory.subList(0, Math.min(MAX_HISTORY, allHistory.size()))); |
| + } |
| + |
| + } |
| + |
| + public List<String> getHistory() { |
| + return history; |
| + } |
| + |
| + @Override |
| + public void addHistory(String indexPath) throws IOException { |
| + if (history.indexOf(indexPath) >= 0) { |
| + history.remove(indexPath); |
| + } |
| + history.add(0, indexPath); |
| + saveHistory(); |
| + } |
| + |
| + private void saveHistory() throws IOException { |
| + Files.write(historyFile(), history); |
| + } |
| + |
| + private Path historyFile() { |
| + return FileSystems.getDefault().getPath(CONFIG_DIR, HISTORY_FILE); |
| + } |
| + |
| + @Override |
| + public ColorTheme getColorTheme() { |
| + String theme = ini.getString("settings", "theme"); |
| + return (theme == null) ? ColorTheme.GRAY : ColorTheme.valueOf(theme); |
| + } |
| + |
| + @Override |
| + public void setColorTheme(ColorTheme theme) throws IOException { |
| + ini.put("settings", "theme", theme.name()); |
| + ini.store(iniFile()); |
| + } |
| + |
| + @Override |
| + public boolean isReadOnly() { |
| + Boolean readOnly = ini.getBoolean("opener", "readOnly"); |
| + return (readOnly == null) ? false : readOnly; |
| + } |
| + |
| + @Override |
| + public String getDirImpl() { |
| + String dirImpl = ini.getString("opener", "dirImpl"); |
| + return (dirImpl == null) ? FSDirectory.class.getName() : dirImpl; |
| + } |
| + |
| + @Override |
| + public boolean isNoReader() { |
| + Boolean noReader = ini.getBoolean("opener", "noReader"); |
| + return (noReader == null) ? false : noReader; |
| + } |
| + |
| + @Override |
| + public boolean isUseCompound() { |
| + Boolean useCompound = ini.getBoolean("opener", "useCompound"); |
| + return (useCompound == null) ? false : useCompound; |
| + } |
| + |
| + @Override |
| + public boolean isKeepAllCommits() { |
| + Boolean keepAllCommits = ini.getBoolean("opener", "keepAllCommits"); |
| + return (keepAllCommits == null) ? false : keepAllCommits; |
| + } |
| + |
| + @Override |
| + public void setIndexOpenerPrefs(boolean readOnly, String dirImpl, boolean noReader, boolean useCompound, boolean keepAllCommits) throws IOException { |
| + ini.put("opener", "readOnly", readOnly); |
| + ini.put("opener", "dirImpl", dirImpl); |
| + ini.put("opener", "noReader", noReader); |
| + ini.put("opener", "useCompound", useCompound); |
| + ini.put("opener", "keepAllCommits", keepAllCommits); |
| + ini.store(iniFile()); |
| + } |
| + |
| + private Path iniFile() { |
| + return FileSystems.getDefault().getPath(CONFIG_DIR, INIT_FILE); |
| + } |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/AnalysisPanelProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/AnalysisPanelProvider.java |
| new file mode 100644 |
| index 00000000000..70c2291bbca |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/AnalysisPanelProvider.java |
| @@ -0,0 +1,441 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components; |
| + |
| +import javax.swing.BorderFactory; |
| +import javax.swing.ButtonGroup; |
| +import javax.swing.JButton; |
| +import javax.swing.JLabel; |
| +import javax.swing.JPanel; |
| +import javax.swing.JRadioButton; |
| +import javax.swing.JScrollPane; |
| +import javax.swing.JSplitPane; |
| +import javax.swing.JTable; |
| +import javax.swing.JTextArea; |
| +import javax.swing.ListSelectionModel; |
| +import java.awt.BorderLayout; |
| +import java.awt.Color; |
| +import java.awt.FlowLayout; |
| +import java.awt.GridLayout; |
| +import java.awt.Insets; |
| +import java.awt.event.ActionEvent; |
| +import java.awt.event.MouseAdapter; |
| +import java.awt.event.MouseEvent; |
| +import java.io.IOException; |
| +import java.util.List; |
| +import java.util.Objects; |
| +import java.util.concurrent.ExecutorService; |
| +import java.util.concurrent.Executors; |
| +import java.util.stream.Collectors; |
| + |
| +import org.apache.lucene.analysis.Analyzer; |
| +import org.apache.lucene.analysis.custom.CustomAnalyzer; |
| +import org.apache.lucene.analysis.standard.StandardAnalyzer; |
| +import org.apache.lucene.luke.app.desktop.MessageBroker; |
| +import org.apache.lucene.luke.app.desktop.components.dialog.analysis.AnalysisChainDialogFactory; |
| +import org.apache.lucene.luke.app.desktop.components.dialog.analysis.TokenAttributeDialogFactory; |
| +import org.apache.lucene.luke.app.desktop.components.dialog.documents.AddDocumentDialogOperator; |
| +import org.apache.lucene.luke.app.desktop.components.fragments.analysis.CustomAnalyzerPanelOperator; |
| +import org.apache.lucene.luke.app.desktop.components.fragments.analysis.CustomAnalyzerPanelProvider; |
| +import org.apache.lucene.luke.app.desktop.components.fragments.analysis.PresetAnalyzerPanelOperator; |
| +import org.apache.lucene.luke.app.desktop.components.fragments.analysis.PresetAnalyzerPanelProvider; |
| +import org.apache.lucene.luke.app.desktop.components.fragments.search.AnalyzerTabOperator; |
| +import org.apache.lucene.luke.app.desktop.components.fragments.search.MLTTabOperator; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.FontUtils; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.StyleConstants; |
| +import org.apache.lucene.luke.app.desktop.util.TableUtils; |
| +import org.apache.lucene.luke.models.analysis.Analysis; |
| +import org.apache.lucene.luke.models.analysis.AnalysisFactory; |
| +import org.apache.lucene.luke.models.analysis.CustomAnalyzerConfig; |
| +import org.apache.lucene.util.NamedThreadFactory; |
| + |
| +/** Provider of the Analysis panel */ |
| +public final class AnalysisPanelProvider implements AnalysisTabOperator { |
| + |
| + private static final String TYPE_PRESET = "preset"; |
| + |
| + private static final String TYPE_CUSTOM = "custom"; |
| + |
| + private final ComponentOperatorRegistry operatorRegistry; |
| + |
| + private final AnalysisChainDialogFactory analysisChainDialogFactory; |
| + |
| + private final TokenAttributeDialogFactory tokenAttrDialogFactory; |
| + |
| + private final MessageBroker messageBroker; |
| + |
| + private final JPanel mainPanel = new JPanel(); |
| + |
| + private final JPanel preset; |
| + |
| + private final JPanel custom; |
| + |
| + private final JRadioButton presetRB = new JRadioButton(); |
| + |
| + private final JRadioButton customRB = new JRadioButton(); |
| + |
| + private final JLabel analyzerNameLbl = new JLabel(); |
| + |
| + private final JLabel showChainLbl = new JLabel(); |
| + |
| + private final JTextArea inputArea = new JTextArea(); |
| + |
| + private final JTable tokensTable = new JTable(); |
| + |
| + private final ListenerFunctions listeners = new ListenerFunctions(); |
| + |
| + private List<Analysis.Token> tokens; |
| + |
| + private Analysis analysisModel; |
| + |
| + public AnalysisPanelProvider() throws IOException { |
| + this.preset = new PresetAnalyzerPanelProvider().get(); |
| + this.custom = new CustomAnalyzerPanelProvider().get(); |
| + |
| + this.operatorRegistry = ComponentOperatorRegistry.getInstance(); |
| + this.analysisChainDialogFactory = AnalysisChainDialogFactory.getInstance(); |
| + this.tokenAttrDialogFactory = TokenAttributeDialogFactory.getInstance(); |
| + this.messageBroker = MessageBroker.getInstance(); |
| + |
| + this.analysisModel = new AnalysisFactory().newInstance(); |
| + analysisModel.createAnalyzerFromClassName(StandardAnalyzer.class.getName()); |
| + |
| + operatorRegistry.register(AnalysisTabOperator.class, this); |
| + |
| + operatorRegistry.get(PresetAnalyzerPanelOperator.class).ifPresent(operator -> { |
| + // Scanning all Analyzer types will take time... |
| + ExecutorService executorService = Executors.newFixedThreadPool(1, new NamedThreadFactory("load-preset-analyzer-types")); |
| + executorService.execute(() -> { |
| + operator.setPresetAnalyzers(analysisModel.getPresetAnalyzerTypes()); |
| + operator.setSelectedAnalyzer(analysisModel.currentAnalyzer().getClass()); |
| + }); |
| + executorService.shutdown(); |
| + }); |
| + } |
| + |
| + public JPanel get() { |
| + JPanel panel = new JPanel(new GridLayout(1, 1)); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createLineBorder(Color.gray)); |
| + |
| + JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, initUpperPanel(), initLowerPanel()); |
| + splitPane.setOpaque(false); |
| + splitPane.setDividerLocation(320); |
| + panel.add(splitPane); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel initUpperPanel() { |
| + mainPanel.setOpaque(false); |
| + mainPanel.setLayout(new BorderLayout()); |
| + mainPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); |
| + |
| + mainPanel.add(initSwitcherPanel(), BorderLayout.PAGE_START); |
| + mainPanel.add(preset, BorderLayout.CENTER); |
| + |
| + return mainPanel; |
| + } |
| + |
| + private JPanel initSwitcherPanel() { |
| + JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + panel.setOpaque(false); |
| + |
| + presetRB.setText(MessageUtils.getLocalizedMessage("analysis.radio.preset")); |
| + presetRB.setActionCommand(TYPE_PRESET); |
| + presetRB.addActionListener(listeners::toggleMainPanel); |
| + presetRB.setOpaque(false); |
| + presetRB.setSelected(true); |
| + |
| + customRB.setText(MessageUtils.getLocalizedMessage("analysis.radio.custom")); |
| + customRB.setActionCommand(TYPE_CUSTOM); |
| + customRB.addActionListener(listeners::toggleMainPanel); |
| + customRB.setOpaque(false); |
| + customRB.setSelected(false); |
| + |
| + ButtonGroup group = new ButtonGroup(); |
| + group.add(presetRB); |
| + group.add(customRB); |
| + |
| + panel.add(presetRB); |
| + panel.add(customRB); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel initLowerPanel() { |
| + JPanel inner1 = new JPanel(new BorderLayout()); |
| + inner1.setOpaque(false); |
| + |
| + JPanel analyzerName = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 2)); |
| + analyzerName.setOpaque(false); |
| + analyzerName.add(new JLabel(MessageUtils.getLocalizedMessage("analysis.label.selected_analyzer"))); |
| + analyzerNameLbl.setText(analysisModel.currentAnalyzer().getClass().getName()); |
| + analyzerName.add(analyzerNameLbl); |
| + showChainLbl.setText(MessageUtils.getLocalizedMessage("analysis.label.show_chain")); |
| + showChainLbl.addMouseListener(new MouseAdapter() { |
| + @Override |
| + public void mouseClicked(MouseEvent e) { |
| + listeners.showAnalysisChain(e); |
| + } |
| + }); |
| + showChainLbl.setVisible(analysisModel.currentAnalyzer() instanceof CustomAnalyzer); |
| + analyzerName.add(FontUtils.toLinkText(showChainLbl)); |
| + inner1.add(analyzerName, BorderLayout.PAGE_START); |
| + |
| + JPanel input = new JPanel(new FlowLayout(FlowLayout.LEADING, 5, 2)); |
| + input.setOpaque(false); |
| + inputArea.setRows(3); |
| + inputArea.setColumns(50); |
| + inputArea.setLineWrap(true); |
| + inputArea.setWrapStyleWord(true); |
| + inputArea.setText(MessageUtils.getLocalizedMessage("analysis.textarea.prompt")); |
| + input.add(new JScrollPane(inputArea)); |
| + |
| + JButton executeBtn = new JButton(FontUtils.elegantIconHtml("", MessageUtils.getLocalizedMessage("analysis.button.test"))); |
| + executeBtn.setFont(StyleConstants.FONT_BUTTON_LARGE); |
| + executeBtn.setMargin(new Insets(3, 3, 3, 3)); |
| + executeBtn.addActionListener(listeners::executeAnalysis); |
| + input.add(executeBtn); |
| + |
| + JButton clearBtn = new JButton(MessageUtils.getLocalizedMessage("button.clear")); |
| + clearBtn.setFont(StyleConstants.FONT_BUTTON_LARGE); |
| + clearBtn.setMargin(new Insets(5, 5, 5, 5)); |
| + clearBtn.addActionListener(e -> { |
| + inputArea.setText(""); |
| + TableUtils.setupTable(tokensTable, ListSelectionModel.SINGLE_SELECTION, new TokensTableModel(), |
| + null, |
| + TokensTableModel.Column.TERM.getColumnWidth(), |
| + TokensTableModel.Column.ATTR.getColumnWidth()); |
| + }); |
| + input.add(clearBtn); |
| + |
| + inner1.add(input, BorderLayout.CENTER); |
| + |
| + JPanel inner2 = new JPanel(new BorderLayout()); |
| + inner2.setOpaque(false); |
| + |
| + JPanel hint = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + hint.setOpaque(false); |
| + hint.add(new JLabel(MessageUtils.getLocalizedMessage("analysis.hint.show_attributes"))); |
| + inner2.add(hint, BorderLayout.PAGE_START); |
| + |
| + |
| + TableUtils.setupTable(tokensTable, ListSelectionModel.SINGLE_SELECTION, new TokensTableModel(), |
| + new MouseAdapter() { |
| + @Override |
| + public void mouseClicked(MouseEvent e) { |
| + listeners.showAttributeValues(e); |
| + } |
| + }, |
| + TokensTableModel.Column.TERM.getColumnWidth(), |
| + TokensTableModel.Column.ATTR.getColumnWidth()); |
| + inner2.add(new JScrollPane(tokensTable), BorderLayout.CENTER); |
| + |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); |
| + panel.add(inner1, BorderLayout.PAGE_START); |
| + panel.add(inner2, BorderLayout.CENTER); |
| + |
| + return panel; |
| + } |
| + |
| + // control methods |
| + |
| + void toggleMainPanel(String command) { |
| + if (command.equalsIgnoreCase(TYPE_PRESET)) { |
| + mainPanel.remove(custom); |
| + mainPanel.add(preset, BorderLayout.CENTER); |
| + |
| + operatorRegistry.get(PresetAnalyzerPanelOperator.class).ifPresent(operator -> { |
| + operator.setPresetAnalyzers(analysisModel.getPresetAnalyzerTypes()); |
| + operator.setSelectedAnalyzer(analysisModel.currentAnalyzer().getClass()); |
| + }); |
| + |
| + } else if (command.equalsIgnoreCase(TYPE_CUSTOM)) { |
| + mainPanel.remove(preset); |
| + mainPanel.add(custom, BorderLayout.CENTER); |
| + |
| + operatorRegistry.get(CustomAnalyzerPanelOperator.class).ifPresent(operator -> { |
| + operator.setAnalysisModel(analysisModel); |
| + operator.resetAnalysisComponents(); |
| + }); |
| + } |
| + mainPanel.setVisible(false); |
| + mainPanel.setVisible(true); |
| + } |
| + |
| + void executeAnalysis() { |
| + String text = inputArea.getText(); |
| + if (Objects.isNull(text) || text.isEmpty()) { |
| + messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("analysis.message.empry_input")); |
| + } |
| + |
| + tokens = analysisModel.analyze(text); |
| + tokensTable.setModel(new TokensTableModel(tokens)); |
| + tokensTable.setShowGrid(true); |
| + tokensTable.getColumnModel().getColumn(TokensTableModel.Column.TERM.getIndex()).setPreferredWidth(TokensTableModel.Column.TERM.getColumnWidth()); |
| + tokensTable.getColumnModel().getColumn(TokensTableModel.Column.ATTR.getIndex()).setPreferredWidth(TokensTableModel.Column.ATTR.getColumnWidth()); |
| + } |
| + |
| + void showAnalysisChainDialog() { |
| + if (getCurrentAnalyzer() instanceof CustomAnalyzer) { |
| + CustomAnalyzer analyzer = (CustomAnalyzer) getCurrentAnalyzer(); |
| + new DialogOpener<>(analysisChainDialogFactory).open("Analysis chain", 600, 320, |
| + (factory) -> { |
| + factory.setAnalyzer(analyzer); |
| + }); |
| + } |
| + } |
| + |
| + void showAttributeValues(int selectedIndex) { |
| + String term = tokens.get(selectedIndex).getTerm(); |
| + List<Analysis.TokenAttribute> attributes = tokens.get(selectedIndex).getAttributes(); |
| + new DialogOpener<>(tokenAttrDialogFactory).open("Token Attributes", 650, 400, |
| + factory -> { |
| + factory.setTerm(term); |
| + factory.setAttributes(attributes); |
| + }); |
| + } |
| + |
| + |
| + @Override |
| + public void setAnalyzerByType(String analyzerType) { |
| + analysisModel.createAnalyzerFromClassName(analyzerType); |
| + analyzerNameLbl.setText(analysisModel.currentAnalyzer().getClass().getName()); |
| + showChainLbl.setVisible(false); |
| + operatorRegistry.get(AnalyzerTabOperator.class).ifPresent(operator -> |
| + operator.setAnalyzer(analysisModel.currentAnalyzer())); |
| + operatorRegistry.get(MLTTabOperator.class).ifPresent(operator -> |
| + operator.setAnalyzer(analysisModel.currentAnalyzer())); |
| + operatorRegistry.get(AddDocumentDialogOperator.class).ifPresent(operator -> |
| + operator.setAnalyzer(analysisModel.currentAnalyzer())); |
| + } |
| + |
| + @Override |
| + public void setAnalyzerByCustomConfiguration(CustomAnalyzerConfig config) { |
| + analysisModel.buildCustomAnalyzer(config); |
| + analyzerNameLbl.setText(analysisModel.currentAnalyzer().getClass().getName()); |
| + showChainLbl.setVisible(true); |
| + operatorRegistry.get(AnalyzerTabOperator.class).ifPresent(operator -> |
| + operator.setAnalyzer(analysisModel.currentAnalyzer())); |
| + operatorRegistry.get(MLTTabOperator.class).ifPresent(operator -> |
| + operator.setAnalyzer(analysisModel.currentAnalyzer())); |
| + operatorRegistry.get(AddDocumentDialogOperator.class).ifPresent(operator -> |
| + operator.setAnalyzer(analysisModel.currentAnalyzer())); |
| + } |
| + |
| + @Override |
| + public Analyzer getCurrentAnalyzer() { |
| + return analysisModel.currentAnalyzer(); |
| + } |
| + |
| + private class ListenerFunctions { |
| + |
| + void toggleMainPanel(ActionEvent e) { |
| + AnalysisPanelProvider.this.toggleMainPanel(e.getActionCommand()); |
| + } |
| + |
| + void showAnalysisChain(MouseEvent e) { |
| + AnalysisPanelProvider.this.showAnalysisChainDialog(); |
| + } |
| + |
| + void executeAnalysis(ActionEvent e) { |
| + AnalysisPanelProvider.this.executeAnalysis(); |
| + } |
| + |
| + void showAttributeValues(MouseEvent e) { |
| + if (e.getClickCount() != 2 || e.isConsumed()) { |
| + return; |
| + } |
| + int selectedIndex = tokensTable.rowAtPoint(e.getPoint()); |
| + if (selectedIndex < 0 || selectedIndex >= tokensTable.getRowCount()) { |
| + return; |
| + } |
| + AnalysisPanelProvider.this.showAttributeValues(selectedIndex); |
| + } |
| + |
| + } |
| + |
| + static final class TokensTableModel extends TableModelBase<TokensTableModel.Column> { |
| + |
| + enum Column implements TableColumnInfo { |
| + TERM("Term", 0, String.class, 150), |
| + ATTR("Attributes", 1, String.class, 1000); |
| + |
| + private final String colName; |
| + private final int index; |
| + private final Class<?> type; |
| + private final int width; |
| + |
| + Column(String colName, int index, Class<?> type, int width) { |
| + this.colName = colName; |
| + this.index = index; |
| + this.type = type; |
| + this.width = width; |
| + } |
| + |
| + @Override |
| + public String getColName() { |
| + return colName; |
| + } |
| + |
| + @Override |
| + public int getIndex() { |
| + return index; |
| + } |
| + |
| + @Override |
| + public Class<?> getType() { |
| + return type; |
| + } |
| + |
| + @Override |
| + public int getColumnWidth() { |
| + return width; |
| + } |
| + } |
| + |
| + TokensTableModel() { |
| + super(); |
| + } |
| + |
| + TokensTableModel(List<Analysis.Token> tokens) { |
| + super(tokens.size()); |
| + for (int i = 0; i < tokens.size(); i++) { |
| + Analysis.Token token = tokens.get(i); |
| + data[i][Column.TERM.getIndex()] = token.getTerm(); |
| + List<String> attValues = token.getAttributes().stream() |
| + .flatMap(att -> att.getAttValues().entrySet().stream() |
| + .map(e -> e.getKey() + "=" + e.getValue())) |
| + .collect(Collectors.toList()); |
| + data[i][Column.ATTR.getIndex()] = String.join(",", attValues); |
| + } |
| + } |
| + |
| + @Override |
| + protected Column[] columnInfos() { |
| + return Column.values(); |
| + } |
| + } |
| + |
| +} |
| + |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/AnalysisTabOperator.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/AnalysisTabOperator.java |
| new file mode 100644 |
| index 00000000000..555f1c0245c |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/AnalysisTabOperator.java |
| @@ -0,0 +1,33 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components; |
| + |
| +import org.apache.lucene.analysis.Analyzer; |
| +import org.apache.lucene.luke.models.analysis.CustomAnalyzerConfig; |
| + |
| +/** Operator for the Analysis tab */ |
| +public interface AnalysisTabOperator extends ComponentOperatorRegistry.ComponentOperator { |
| + |
| + void setAnalyzerByType(String analyzerType); |
| + |
| + void setAnalyzerByCustomConfiguration(CustomAnalyzerConfig config); |
| + |
| + Analyzer getCurrentAnalyzer(); |
| + |
| +} |
| + |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/CommitsPanelProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/CommitsPanelProvider.java |
| new file mode 100644 |
| index 00000000000..d06abcc0789 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/CommitsPanelProvider.java |
| @@ -0,0 +1,575 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components; |
| + |
| +import javax.swing.BorderFactory; |
| +import javax.swing.BoxLayout; |
| +import javax.swing.ButtonGroup; |
| +import javax.swing.DefaultComboBoxModel; |
| +import javax.swing.DefaultListModel; |
| +import javax.swing.JComboBox; |
| +import javax.swing.JLabel; |
| +import javax.swing.JList; |
| +import javax.swing.JPanel; |
| +import javax.swing.JRadioButton; |
| +import javax.swing.JScrollPane; |
| +import javax.swing.JSplitPane; |
| +import javax.swing.JTable; |
| +import javax.swing.JTextArea; |
| +import javax.swing.ListSelectionModel; |
| +import java.awt.BorderLayout; |
| +import java.awt.Color; |
| +import java.awt.FlowLayout; |
| +import java.awt.GridBagConstraints; |
| +import java.awt.GridBagLayout; |
| +import java.awt.GridLayout; |
| +import java.awt.event.ActionEvent; |
| +import java.awt.event.MouseAdapter; |
| +import java.awt.event.MouseEvent; |
| +import java.util.HashMap; |
| +import java.util.List; |
| +import java.util.Map; |
| + |
| +import org.apache.lucene.index.DirectoryReader; |
| +import org.apache.lucene.luke.app.DirectoryHandler; |
| +import org.apache.lucene.luke.app.DirectoryObserver; |
| +import org.apache.lucene.luke.app.IndexHandler; |
| +import org.apache.lucene.luke.app.IndexObserver; |
| +import org.apache.lucene.luke.app.LukeState; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.TableUtils; |
| +import org.apache.lucene.luke.models.commits.Commit; |
| +import org.apache.lucene.luke.models.commits.Commits; |
| +import org.apache.lucene.luke.models.commits.CommitsFactory; |
| +import org.apache.lucene.luke.models.commits.File; |
| +import org.apache.lucene.luke.models.commits.Segment; |
| + |
| +/** Provider of the Commits panel */ |
| +public final class CommitsPanelProvider { |
| + |
| + private final CommitsFactory commitsFactory = new CommitsFactory(); |
| + |
| + private final JComboBox<Long> commitGenCombo = new JComboBox<>(); |
| + |
| + private final JLabel deletedLbl = new JLabel(); |
| + |
| + private final JLabel segCntLbl = new JLabel(); |
| + |
| + private final JTextArea userDataTA = new JTextArea(); |
| + |
| + private final JTable filesTable = new JTable(); |
| + |
| + private final JTable segmentsTable = new JTable(); |
| + |
| + private final JRadioButton diagRB = new JRadioButton(); |
| + |
| + private final JRadioButton attrRB = new JRadioButton(); |
| + |
| + private final JRadioButton codecRB = new JRadioButton(); |
| + |
| + private final ButtonGroup rbGroup = new ButtonGroup(); |
| + |
| + private final JList<String> segDetailList = new JList<>(); |
| + |
| + private ListenerFunctions listeners = new ListenerFunctions(); |
| + |
| + private Commits commitsModel; |
| + |
| + public CommitsPanelProvider() { |
| + IndexHandler.getInstance().addObserver(new Observer()); |
| + DirectoryHandler.getInstance().addObserver(new Observer()); |
| + } |
| + |
| + public JPanel get() { |
| + JPanel panel = new JPanel(new GridLayout(1, 1)); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createLineBorder(Color.gray)); |
| + |
| + JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, initUpperPanel(), initLowerPanel()); |
| + splitPane.setOpaque(false); |
| + splitPane.setBorder(BorderFactory.createEmptyBorder()); |
| + splitPane.setDividerLocation(120); |
| + panel.add(splitPane); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel initUpperPanel() { |
| + JPanel panel = new JPanel(new BorderLayout(20, 0)); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); |
| + |
| + JPanel left = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + left.setOpaque(false); |
| + left.add(new JLabel(MessageUtils.getLocalizedMessage("commits.label.select_gen"))); |
| + commitGenCombo.addActionListener(listeners::selectGeneration); |
| + left.add(commitGenCombo); |
| + panel.add(left, BorderLayout.LINE_START); |
| + |
| + JPanel right = new JPanel(new GridBagLayout()); |
| + right.setOpaque(false); |
| + GridBagConstraints c1 = new GridBagConstraints(); |
| + c1.ipadx = 5; |
| + c1.ipady = 5; |
| + |
| + c1.gridx = 0; |
| + c1.gridy = 0; |
| + c1.weightx = 0.2; |
| + c1.anchor = GridBagConstraints.EAST; |
| + right.add(new JLabel(MessageUtils.getLocalizedMessage("commits.label.deleted")), c1); |
| + |
| + c1.gridx = 1; |
| + c1.gridy = 0; |
| + c1.weightx = 0.5; |
| + c1.anchor = GridBagConstraints.WEST; |
| + right.add(deletedLbl, c1); |
| + |
| + c1.gridx = 0; |
| + c1.gridy = 1; |
| + c1.weightx = 0.2; |
| + c1.anchor = GridBagConstraints.EAST; |
| + right.add(new JLabel(MessageUtils.getLocalizedMessage("commits.label.segcount")), c1); |
| + |
| + c1.gridx = 1; |
| + c1.gridy = 1; |
| + c1.weightx = 0.5; |
| + c1.anchor = GridBagConstraints.WEST; |
| + right.add(segCntLbl, c1); |
| + |
| + c1.gridx = 0; |
| + c1.gridy = 2; |
| + c1.weightx = 0.2; |
| + c1.anchor = GridBagConstraints.EAST; |
| + right.add(new JLabel(MessageUtils.getLocalizedMessage("commits.label.userdata")), c1); |
| + |
| + userDataTA.setRows(3); |
| + userDataTA.setColumns(30); |
| + userDataTA.setLineWrap(true); |
| + userDataTA.setWrapStyleWord(true); |
| + userDataTA.setEditable(false); |
| + JScrollPane userDataScroll = new JScrollPane(userDataTA, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); |
| + c1.gridx = 1; |
| + c1.gridy = 2; |
| + c1.weightx = 0.5; |
| + c1.anchor = GridBagConstraints.WEST; |
| + right.add(userDataScroll, c1); |
| + |
| + panel.add(right, BorderLayout.CENTER); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel initLowerPanel() { |
| + JPanel panel = new JPanel(new GridLayout(1, 1)); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); |
| + |
| + JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, initFilesPanel(), initSegmentsPanel()); |
| + splitPane.setOpaque(false); |
| + splitPane.setBorder(BorderFactory.createEmptyBorder()); |
| + splitPane.setDividerLocation(300); |
| + panel.add(splitPane); |
| + return panel; |
| + } |
| + |
| + private JPanel initFilesPanel() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); |
| + |
| + JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + header.setOpaque(false); |
| + header.add(new JLabel(MessageUtils.getLocalizedMessage("commits.label.files"))); |
| + panel.add(header, BorderLayout.PAGE_START); |
| + |
| + TableUtils.setupTable(filesTable, ListSelectionModel.SINGLE_SELECTION, new FilesTableModel(), null, FilesTableModel.Column.FILENAME.getColumnWidth()); |
| + panel.add(new JScrollPane(filesTable), BorderLayout.CENTER); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel initSegmentsPanel() { |
| + JPanel panel = new JPanel(); |
| + panel.setOpaque(false); |
| + panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); |
| + |
| + JPanel segments = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + segments.setOpaque(false); |
| + segments.add(new JLabel(MessageUtils.getLocalizedMessage("commits.label.segments"))); |
| + panel.add(segments); |
| + |
| + TableUtils.setupTable(segmentsTable, ListSelectionModel.SINGLE_SELECTION, new SegmentsTableModel(), |
| + new MouseAdapter() { |
| + @Override |
| + public void mouseClicked(MouseEvent e) { |
| + listeners.showSegmentDetails(e); |
| + } |
| + }, |
| + SegmentsTableModel.Column.NAME.getColumnWidth(), |
| + SegmentsTableModel.Column.MAXDOCS.getColumnWidth(), |
| + SegmentsTableModel.Column.DELS.getColumnWidth(), |
| + SegmentsTableModel.Column.DELGEN.getColumnWidth(), |
| + SegmentsTableModel.Column.VERSION.getColumnWidth(), |
| + SegmentsTableModel.Column.CODEC.getColumnWidth()); |
| + panel.add(new JScrollPane(segmentsTable)); |
| + |
| + JPanel segDetails = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + segDetails.setOpaque(false); |
| + segDetails.add(new JLabel(MessageUtils.getLocalizedMessage("commits.label.segdetails"))); |
| + panel.add(segDetails); |
| + |
| + JPanel buttons = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + buttons.setOpaque(false); |
| + |
| + diagRB.setText("Diagnostics"); |
| + diagRB.setActionCommand(ActionCommand.DIAGNOSTICS.name()); |
| + diagRB.setSelected(true); |
| + diagRB.setEnabled(false); |
| + diagRB.setOpaque(false); |
| + diagRB.addMouseListener(new MouseAdapter() { |
| + @Override |
| + public void mouseClicked(MouseEvent e) { |
| + listeners.showSegmentDetails(e); |
| + } |
| + }); |
| + buttons.add(diagRB); |
| + |
| + attrRB.setText("Attributes"); |
| + attrRB.setActionCommand(ActionCommand.ATTRIBUTES.name()); |
| + attrRB.setSelected(false); |
| + attrRB.setEnabled(false); |
| + attrRB.setOpaque(false); |
| + attrRB.addMouseListener(new MouseAdapter() { |
| + @Override |
| + public void mouseClicked(MouseEvent e) { |
| + listeners.showSegmentDetails(e); |
| + } |
| + }); |
| + buttons.add(attrRB); |
| + |
| + codecRB.setText("Codec"); |
| + codecRB.setActionCommand(ActionCommand.CODEC.name()); |
| + codecRB.setSelected(false); |
| + codecRB.setEnabled(false); |
| + codecRB.setOpaque(false); |
| + codecRB.addMouseListener(new MouseAdapter() { |
| + @Override |
| + public void mouseClicked(MouseEvent e) { |
| + listeners.showSegmentDetails(e); |
| + } |
| + }); |
| + buttons.add(codecRB); |
| + |
| + rbGroup.add(diagRB); |
| + rbGroup.add(attrRB); |
| + rbGroup.add(codecRB); |
| + |
| + panel.add(buttons); |
| + |
| + segDetailList.setVisibleRowCount(10); |
| + panel.add(new JScrollPane(segDetailList)); |
| + |
| + return panel; |
| + } |
| + |
| + // control methods |
| + |
| + private void selectGeneration() { |
| + diagRB.setEnabled(false); |
| + attrRB.setEnabled(false); |
| + codecRB.setEnabled(false); |
| + segDetailList.setModel(new DefaultListModel<>()); |
| + |
| + long commitGen = (long) commitGenCombo.getSelectedItem(); |
| + commitsModel.getCommit(commitGen).ifPresent(commit -> { |
| + deletedLbl.setText(String.valueOf(commit.isDeleted())); |
| + segCntLbl.setText(String.valueOf(commit.getSegCount())); |
| + userDataTA.setText(commit.getUserData()); |
| + }); |
| + |
| + filesTable.setModel(new FilesTableModel(commitsModel.getFiles(commitGen))); |
| + filesTable.setShowGrid(true); |
| + filesTable.getColumnModel().getColumn(FilesTableModel.Column.FILENAME.getIndex()).setPreferredWidth(FilesTableModel.Column.FILENAME.getColumnWidth()); |
| + |
| + segmentsTable.setModel(new SegmentsTableModel(commitsModel.getSegments(commitGen))); |
| + segmentsTable.setShowGrid(true); |
| + segmentsTable.getColumnModel().getColumn(SegmentsTableModel.Column.NAME.getIndex()).setPreferredWidth(SegmentsTableModel.Column.NAME.getColumnWidth()); |
| + segmentsTable.getColumnModel().getColumn(SegmentsTableModel.Column.MAXDOCS.getIndex()).setPreferredWidth(SegmentsTableModel.Column.MAXDOCS.getColumnWidth()); |
| + segmentsTable.getColumnModel().getColumn(SegmentsTableModel.Column.DELS.getIndex()).setPreferredWidth(SegmentsTableModel.Column.DELS.getColumnWidth()); |
| + segmentsTable.getColumnModel().getColumn(SegmentsTableModel.Column.DELGEN.getIndex()).setPreferredWidth(SegmentsTableModel.Column.DELGEN.getColumnWidth()); |
| + segmentsTable.getColumnModel().getColumn(SegmentsTableModel.Column.VERSION.getIndex()).setPreferredWidth(SegmentsTableModel.Column.VERSION.getColumnWidth()); |
| + segmentsTable.getColumnModel().getColumn(SegmentsTableModel.Column.CODEC.getIndex()).setPreferredWidth(SegmentsTableModel.Column.CODEC.getColumnWidth()); |
| + } |
| + |
| + private void showSegmentDetails() { |
| + int selectedRow = segmentsTable.getSelectedRow(); |
| + if (commitGenCombo.getSelectedItem() == null || |
| + selectedRow < 0 || selectedRow >= segmentsTable.getRowCount()) { |
| + return; |
| + } |
| + |
| + diagRB.setEnabled(true); |
| + attrRB.setEnabled(true); |
| + codecRB.setEnabled(true); |
| + |
| + long commitGen = (long) commitGenCombo.getSelectedItem(); |
| + String segName = (String) segmentsTable.getValueAt(selectedRow, SegmentsTableModel.Column.NAME.getIndex()); |
| + ActionCommand command = ActionCommand.valueOf(rbGroup.getSelection().getActionCommand()); |
| + |
| + final DefaultListModel<String> detailsModel = new DefaultListModel<>(); |
| + switch (command) { |
| + case DIAGNOSTICS: |
| + commitsModel.getSegmentDiagnostics(commitGen, segName).entrySet().stream() |
| + .map(entry -> entry.getKey() + " = " + entry.getValue()) |
| + .forEach(detailsModel::addElement); |
| + break; |
| + case ATTRIBUTES: |
| + commitsModel.getSegmentAttributes(commitGen, segName).entrySet().stream() |
| + .map(entry -> entry.getKey() + " = " + entry.getValue()) |
| + .forEach(detailsModel::addElement); |
| + break; |
| + case CODEC: |
| + commitsModel.getSegmentCodec(commitGen, segName).ifPresent(codec -> { |
| + Map<String, String> map = new HashMap<>(); |
| + map.put("Codec name", codec.getName()); |
| + map.put("Codec class name", codec.getClass().getName()); |
| + map.put("Compound format", codec.compoundFormat().getClass().getName()); |
| + map.put("DocValues format", codec.docValuesFormat().getClass().getName()); |
| + map.put("FieldInfos format", codec.fieldInfosFormat().getClass().getName()); |
| + map.put("LiveDocs format", codec.liveDocsFormat().getClass().getName()); |
| + map.put("Norms format", codec.normsFormat().getClass().getName()); |
| + map.put("Points format", codec.pointsFormat().getClass().getName()); |
| + map.put("Postings format", codec.postingsFormat().getClass().getName()); |
| + map.put("SegmentInfo format", codec.segmentInfoFormat().getClass().getName()); |
| + map.put("StoredFields format", codec.storedFieldsFormat().getClass().getName()); |
| + map.put("TermVectors format", codec.termVectorsFormat().getClass().getName()); |
| + map.entrySet().stream() |
| + .map(entry -> entry.getKey() + " = " + entry.getValue()).forEach(detailsModel::addElement); |
| + }); |
| + break; |
| + } |
| + segDetailList.setModel(detailsModel); |
| + |
| + } |
| + |
| + private class ListenerFunctions { |
| + |
| + void selectGeneration(ActionEvent e) { |
| + CommitsPanelProvider.this.selectGeneration(); |
| + } |
| + |
| + void showSegmentDetails(MouseEvent e) { |
| + CommitsPanelProvider.this.showSegmentDetails(); |
| + } |
| + |
| + } |
| + |
| + private class Observer implements IndexObserver, DirectoryObserver { |
| + |
| + @Override |
| + public void openDirectory(LukeState state) { |
| + commitsModel = commitsFactory.newInstance(state.getDirectory(), state.getIndexPath()); |
| + populateCommitGenerations(); |
| + } |
| + |
| + @Override |
| + public void closeDirectory() { |
| + close(); |
| + } |
| + |
| + @Override |
| + public void openIndex(LukeState state) { |
| + if (state.hasDirectoryReader()) { |
| + DirectoryReader dr = (DirectoryReader) state.getIndexReader(); |
| + commitsModel = commitsFactory.newInstance(dr, state.getIndexPath()); |
| + populateCommitGenerations(); |
| + } |
| + } |
| + |
| + @Override |
| + public void closeIndex() { |
| + close(); |
| + } |
| + |
| + private void populateCommitGenerations() { |
| + DefaultComboBoxModel<Long> segGenList = new DefaultComboBoxModel<>(); |
| + for (Commit commit : commitsModel.listCommits()) { |
| + segGenList.addElement(commit.getGeneration()); |
| + } |
| + commitGenCombo.setModel(segGenList); |
| + |
| + if (segGenList.getSize() > 0) { |
| + commitGenCombo.setSelectedIndex(0); |
| + } |
| + } |
| + |
| + private void close() { |
| + commitsModel = null; |
| + |
| + commitGenCombo.setModel(new DefaultComboBoxModel<>()); |
| + deletedLbl.setText(""); |
| + segCntLbl.setText(""); |
| + userDataTA.setText(""); |
| + TableUtils.setupTable(filesTable, ListSelectionModel.SINGLE_SELECTION, new FilesTableModel(), null, FilesTableModel.Column.FILENAME.getColumnWidth()); |
| + TableUtils.setupTable(segmentsTable, ListSelectionModel.SINGLE_SELECTION, new SegmentsTableModel(), null, |
| + SegmentsTableModel.Column.NAME.getColumnWidth(), |
| + SegmentsTableModel.Column.MAXDOCS.getColumnWidth(), |
| + SegmentsTableModel.Column.DELS.getColumnWidth(), |
| + SegmentsTableModel.Column.DELGEN.getColumnWidth(), |
| + SegmentsTableModel.Column.VERSION.getColumnWidth(), |
| + SegmentsTableModel.Column.CODEC.getColumnWidth()); |
| + diagRB.setEnabled(false); |
| + attrRB.setEnabled(false); |
| + codecRB.setEnabled(false); |
| + segDetailList.setModel(new DefaultListModel<>()); |
| + } |
| + } |
| + |
| + enum ActionCommand { |
| + DIAGNOSTICS, ATTRIBUTES, CODEC; |
| + } |
| + |
| + static final class FilesTableModel extends TableModelBase<FilesTableModel.Column> { |
| + |
| + enum Column implements TableColumnInfo { |
| + |
| + FILENAME("Filename", 0, String.class, 200), |
| + SIZE("Size", 1, String.class, Integer.MAX_VALUE); |
| + |
| + private final String colName; |
| + private final int index; |
| + private final Class<?> type; |
| + private final int width; |
| + |
| + Column(String colName, int index, Class<?> type, int width) { |
| + this.colName = colName; |
| + this.index = index; |
| + this.type = type; |
| + this.width = width; |
| + } |
| + |
| + @Override |
| + public String getColName() { |
| + return colName; |
| + } |
| + |
| + @Override |
| + public int getIndex() { |
| + return index; |
| + } |
| + |
| + @Override |
| + public Class<?> getType() { |
| + return type; |
| + } |
| + |
| + @Override |
| + public int getColumnWidth() { |
| + return width; |
| + } |
| + } |
| + |
| + FilesTableModel() { |
| + super(); |
| + } |
| + |
| + FilesTableModel(List<File> files) { |
| + super(files.size()); |
| + for (int i = 0; i < files.size(); i++) { |
| + File file = files.get(i); |
| + data[i][Column.FILENAME.getIndex()] = file.getFileName(); |
| + data[i][Column.SIZE.getIndex()] = file.getDisplaySize(); |
| + } |
| + } |
| + |
| + @Override |
| + protected Column[] columnInfos() { |
| + return Column.values(); |
| + } |
| + } |
| + |
| + static final class SegmentsTableModel extends TableModelBase<SegmentsTableModel.Column> { |
| + |
| + enum Column implements TableColumnInfo { |
| + |
| + NAME("Name", 0, String.class, 60), |
| + MAXDOCS("Max docs", 1, Integer.class, 60), |
| + DELS("Dels", 2, Integer.class, 60), |
| + DELGEN("Del gen", 3, Long.class, 60), |
| + VERSION("Lucene ver.", 4, String.class, 60), |
| + CODEC("Codec", 5, String.class, 100), |
| + SIZE("Size", 6, String.class, 150); |
| + |
| + private final String colName; |
| + private final int index; |
| + private final Class<?> type; |
| + private final int width; |
| + |
| + Column(String colName, int index, Class<?> type, int width) { |
| + this.colName = colName; |
| + this.index = index; |
| + this.type = type; |
| + this.width = width; |
| + } |
| + |
| + @Override |
| + public String getColName() { |
| + return colName; |
| + } |
| + |
| + @Override |
| + public int getIndex() { |
| + return index; |
| + } |
| + |
| + @Override |
| + public Class<?> getType() { |
| + return type; |
| + } |
| + |
| + @Override |
| + public int getColumnWidth() { |
| + return width; |
| + } |
| + } |
| + |
| + SegmentsTableModel() { |
| + super(); |
| + } |
| + |
| + SegmentsTableModel(List<Segment> segments) { |
| + super(segments.size()); |
| + for (int i = 0; i < segments.size(); i++) { |
| + Segment segment = segments.get(i); |
| + data[i][Column.NAME.getIndex()] = segment.getName(); |
| + data[i][Column.MAXDOCS.getIndex()] = segment.getMaxDoc(); |
| + data[i][Column.DELS.getIndex()] = segment.getDelCount(); |
| + data[i][Column.DELGEN.getIndex()] = segment.getDelGen(); |
| + data[i][Column.VERSION.getIndex()] = segment.getLuceneVer(); |
| + data[i][Column.CODEC.getIndex()] = segment.getCodecName(); |
| + data[i][Column.SIZE.getIndex()] = segment.getDisplaySize(); |
| + } |
| + } |
| + |
| + @Override |
| + protected Column[] columnInfos() { |
| + return Column.values(); |
| + } |
| + } |
| +} |
| + |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/ComponentOperatorRegistry.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/ComponentOperatorRegistry.java |
| new file mode 100644 |
| index 00000000000..0d9c99b0ec7 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/ComponentOperatorRegistry.java |
| @@ -0,0 +1,50 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components; |
| + |
| +import java.util.HashMap; |
| +import java.util.Map; |
| +import java.util.Optional; |
| + |
| +/** An utility class for interaction between components */ |
| +public class ComponentOperatorRegistry { |
| + |
| + private static final ComponentOperatorRegistry instance = new ComponentOperatorRegistry(); |
| + |
| + private final Map<Class<?>, Object> operators = new HashMap<>(); |
| + |
| + public static ComponentOperatorRegistry getInstance() { |
| + return instance; |
| + } |
| + |
| + public <T extends ComponentOperator> void register(Class<T> type, T operator) { |
| + if (!operators.containsKey(type)) { |
| + operators.put(type, operator); |
| + } |
| + } |
| + |
| + @SuppressWarnings("unchecked") |
| + public <T extends ComponentOperator> Optional<T> get(Class<T> type) { |
| + return Optional.ofNullable((T) operators.get(type)); |
| + } |
| + |
| + /** marker interface for operators */ |
| + public interface ComponentOperator { |
| + } |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/DocumentsPanelProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/DocumentsPanelProvider.java |
| new file mode 100644 |
| index 00000000000..e9daece4db4 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/DocumentsPanelProvider.java |
| @@ -0,0 +1,1115 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components; |
| + |
| +import javax.swing.BorderFactory; |
| +import javax.swing.BoxLayout; |
| +import javax.swing.JButton; |
| +import javax.swing.JComboBox; |
| +import javax.swing.JComponent; |
| +import javax.swing.JLabel; |
| +import javax.swing.JList; |
| +import javax.swing.JMenuItem; |
| +import javax.swing.JPanel; |
| +import javax.swing.JPopupMenu; |
| +import javax.swing.JScrollPane; |
| +import javax.swing.JSpinner; |
| +import javax.swing.JSplitPane; |
| +import javax.swing.JTable; |
| +import javax.swing.JTextField; |
| +import javax.swing.ListSelectionModel; |
| +import javax.swing.SpinnerModel; |
| +import javax.swing.SpinnerNumberModel; |
| +import javax.swing.event.ChangeEvent; |
| +import javax.swing.table.TableCellRenderer; |
| +import java.awt.BorderLayout; |
| +import java.awt.Color; |
| +import java.awt.Dimension; |
| +import java.awt.FlowLayout; |
| +import java.awt.GridBagConstraints; |
| +import java.awt.GridBagLayout; |
| +import java.awt.GridLayout; |
| +import java.awt.Insets; |
| +import java.awt.Toolkit; |
| +import java.awt.datatransfer.Clipboard; |
| +import java.awt.datatransfer.StringSelection; |
| +import java.awt.event.ActionEvent; |
| +import java.awt.event.MouseAdapter; |
| +import java.awt.event.MouseEvent; |
| +import java.io.IOException; |
| +import java.math.BigDecimal; |
| +import java.math.BigInteger; |
| +import java.util.List; |
| +import java.util.Objects; |
| +import java.util.Optional; |
| + |
| +import org.apache.lucene.index.DocValuesType; |
| +import org.apache.lucene.index.IndexOptions; |
| +import org.apache.lucene.index.Term; |
| +import org.apache.lucene.luke.app.IndexHandler; |
| +import org.apache.lucene.luke.app.IndexObserver; |
| +import org.apache.lucene.luke.app.LukeState; |
| +import org.apache.lucene.luke.app.desktop.MessageBroker; |
| +import org.apache.lucene.luke.app.desktop.components.dialog.HelpDialogFactory; |
| +import org.apache.lucene.luke.app.desktop.components.dialog.documents.AddDocumentDialogFactory; |
| +import org.apache.lucene.luke.app.desktop.components.dialog.documents.DocValuesDialogFactory; |
| +import org.apache.lucene.luke.app.desktop.components.dialog.documents.StoredValueDialogFactory; |
| +import org.apache.lucene.luke.app.desktop.components.dialog.documents.TermVectorDialogFactory; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.FontUtils; |
| +import org.apache.lucene.luke.app.desktop.util.HelpHeaderRenderer; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.StyleConstants; |
| +import org.apache.lucene.luke.app.desktop.util.TableUtils; |
| +import org.apache.lucene.luke.models.documents.DocValues; |
| +import org.apache.lucene.luke.models.documents.DocumentField; |
| +import org.apache.lucene.luke.models.documents.Documents; |
| +import org.apache.lucene.luke.models.documents.DocumentsFactory; |
| +import org.apache.lucene.luke.models.documents.TermPosting; |
| +import org.apache.lucene.luke.models.documents.TermVectorEntry; |
| +import org.apache.lucene.luke.util.BytesRefUtils; |
| + |
| +/** Provider of the Documents panel */ |
| +public final class DocumentsPanelProvider implements DocumentsTabOperator { |
| + |
| + private final DocumentsFactory documentsFactory = new DocumentsFactory(); |
| + |
| + private final MessageBroker messageBroker; |
| + |
| + private final ComponentOperatorRegistry operatorRegistry; |
| + |
| + private final TabSwitcherProxy tabSwitcher; |
| + |
| + private final AddDocumentDialogFactory addDocDialogFactory; |
| + |
| + private final TermVectorDialogFactory tvDialogFactory; |
| + |
| + private final DocValuesDialogFactory dvDialogFactory; |
| + |
| + private final StoredValueDialogFactory valueDialogFactory; |
| + |
| + private final TableCellRenderer tableHeaderRenderer; |
| + |
| + private final JComboBox<String> fieldsCombo = new JComboBox<>(); |
| + |
| + private final JButton firstTermBtn = new JButton(); |
| + |
| + private final JTextField termTF = new JTextField(); |
| + |
| + private final JButton nextTermBtn = new JButton(); |
| + |
| + private final JTextField selectedTermTF = new JTextField(); |
| + |
| + private final JButton firstTermDocBtn = new JButton(); |
| + |
| + private final JTextField termDocIdxTF = new JTextField(); |
| + |
| + private final JButton nextTermDocBtn = new JButton(); |
| + |
| + private final JLabel termDocsNumLbl = new JLabel(); |
| + |
| + private final JTable posTable = new JTable(); |
| + |
| + private final JSpinner docNumSpnr = new JSpinner(); |
| + |
| + private final JLabel maxDocsLbl = new JLabel(); |
| + |
| + private final JButton mltBtn = new JButton(); |
| + |
| + private final JButton addDocBtn = new JButton(); |
| + |
| + private final JButton copyDocValuesBtn = new JButton(); |
| + |
| + private final JTable documentTable = new JTable(); |
| + |
| + private final JPopupMenu documentContextMenu = new JPopupMenu(); |
| + |
| + private final ListenerFunctions listeners = new ListenerFunctions(); |
| + |
| + private Documents documentsModel; |
| + |
| + public DocumentsPanelProvider() throws IOException { |
| + this.messageBroker = MessageBroker.getInstance(); |
| + this.operatorRegistry = ComponentOperatorRegistry.getInstance(); |
| + this.tabSwitcher = TabSwitcherProxy.getInstance(); |
| + this.addDocDialogFactory = AddDocumentDialogFactory.getInstance(); |
| + this.tvDialogFactory = TermVectorDialogFactory.getInstance(); |
| + this.dvDialogFactory = DocValuesDialogFactory.getInstance(); |
| + this.valueDialogFactory = StoredValueDialogFactory.getInstance(); |
| + HelpDialogFactory helpDialogFactory = HelpDialogFactory.getInstance(); |
| + this.tableHeaderRenderer = new HelpHeaderRenderer( |
| + "About Flags", "Format: IdfpoNPSB#txxVDtxxxxTx/x", |
| + createFlagsHelpDialog(), helpDialogFactory); |
| + |
| + IndexHandler.getInstance().addObserver(new Observer()); |
| + operatorRegistry.register(DocumentsTabOperator.class, this); |
| + } |
| + |
| + private JComponent createFlagsHelpDialog() { |
| + String[] values = new String[]{ |
| + "I - index options(docs, frequencies, positions, offsets)", |
| + "N - norms", |
| + "P - payloads", |
| + "S - stored", |
| + "B - binary stored values", |
| + "#txx - numeric stored values(type, precision)", |
| + "V - term vectors", |
| + "Dtxxxxx - doc values(type)", |
| + "Tx/x - point values(num bytes/dimension)" |
| + }; |
| + JList<String> list = new JList<>(values); |
| + return new JScrollPane(list); |
| + } |
| + |
| + public JPanel get() { |
| + JPanel panel = new JPanel(new GridLayout(1, 1)); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createLineBorder(Color.gray)); |
| + |
| + JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, initUpperPanel(), initLowerPanel()); |
| + splitPane.setOpaque(false); |
| + splitPane.setDividerLocation(0.4); |
| + panel.add(splitPane); |
| + |
| + setUpDocumentContextMenu(); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel initUpperPanel() { |
| + JPanel panel = new JPanel(new GridBagLayout()); |
| + panel.setOpaque(false); |
| + GridBagConstraints c = new GridBagConstraints(); |
| + |
| + c.gridx = 0; |
| + c.gridy = 0; |
| + c.weightx = 0.5; |
| + c.anchor = GridBagConstraints.FIRST_LINE_START; |
| + c.fill = GridBagConstraints.HORIZONTAL; |
| + panel.add(initBrowseTermsPanel(), c); |
| + |
| + c.gridx = 1; |
| + c.gridy = 0; |
| + c.weightx = 0.5; |
| + c.anchor = GridBagConstraints.FIRST_LINE_START; |
| + c.fill = GridBagConstraints.HORIZONTAL; |
| + panel.add(initBrowseDocsByTermPanel(), c); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel initBrowseTermsPanel() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); |
| + |
| + JPanel top = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + top.setOpaque(false); |
| + JLabel label = new JLabel(MessageUtils.getLocalizedMessage("documents.label.browse_terms")); |
| + top.add(label); |
| + |
| + panel.add(top, BorderLayout.PAGE_START); |
| + |
| + JPanel center = new JPanel(new GridBagLayout()); |
| + center.setOpaque(false); |
| + GridBagConstraints c = new GridBagConstraints(); |
| + c.fill = GridBagConstraints.BOTH; |
| + |
| + fieldsCombo.addActionListener(listeners::showFirstTerm); |
| + c.gridx = 0; |
| + c.gridy = 0; |
| + c.insets = new Insets(5, 5, 5, 5); |
| + c.weightx = 0.0; |
| + c.gridwidth = 2; |
| + center.add(fieldsCombo, c); |
| + |
| + firstTermBtn.setText(FontUtils.elegantIconHtml("8", MessageUtils.getLocalizedMessage("documents.button.first_term"))); |
| + firstTermBtn.setMaximumSize(new Dimension(80, 30)); |
| + firstTermBtn.addActionListener(listeners::showFirstTerm); |
| + c.gridx = 0; |
| + c.gridy = 1; |
| + c.insets = new Insets(5, 5, 5, 5); |
| + c.weightx = 0.2; |
| + c.gridwidth = 1; |
| + center.add(firstTermBtn, c); |
| + |
| + termTF.setColumns(20); |
| + termTF.setMinimumSize(new Dimension(50, 25)); |
| + termTF.setFont(StyleConstants.FONT_MONOSPACE_LARGE); |
| + termTF.addActionListener(listeners::seekNextTerm); |
| + c.gridx = 1; |
| + c.gridy = 1; |
| + c.insets = new Insets(5, 5, 5, 5); |
| + c.weightx = 0.5; |
| + c.gridwidth = 1; |
| + center.add(termTF, c); |
| + |
| + nextTermBtn.setText(MessageUtils.getLocalizedMessage("documents.button.next")); |
| + nextTermBtn.addActionListener(listeners::showNextTerm); |
| + c.gridx = 2; |
| + c.gridy = 1; |
| + c.insets = new Insets(5, 5, 5, 5); |
| + c.weightx = 0.1; |
| + c.gridwidth = 1; |
| + center.add(nextTermBtn, c); |
| + |
| + panel.add(center, BorderLayout.CENTER); |
| + |
| + JPanel footer = new JPanel(new FlowLayout(FlowLayout.LEADING, 20, 5)); |
| + footer.setOpaque(false); |
| + JLabel hintLbl = new JLabel(MessageUtils.getLocalizedMessage("documents.label.browse_terms_hint")); |
| + footer.add(hintLbl); |
| + panel.add(footer, BorderLayout.PAGE_END); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel initBrowseDocsByTermPanel() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); |
| + |
| + JPanel center = new JPanel(new GridBagLayout()); |
| + center.setOpaque(false); |
| + GridBagConstraints c = new GridBagConstraints(); |
| + c.fill = GridBagConstraints.BOTH; |
| + |
| + JLabel label = new JLabel(MessageUtils.getLocalizedMessage("documents.label.browse_doc_by_term")); |
| + c.gridx = 0; |
| + c.gridy = 0; |
| + c.weightx = 0.0; |
| + c.gridwidth = 2; |
| + c.insets = new Insets(5, 5, 5, 5); |
| + center.add(label, c); |
| + |
| + selectedTermTF.setColumns(20); |
| + selectedTermTF.setFont(StyleConstants.FONT_MONOSPACE_LARGE); |
| + selectedTermTF.setEditable(false); |
| + selectedTermTF.setBackground(Color.white); |
| + c.gridx = 0; |
| + c.gridy = 1; |
| + c.weightx = 0.0; |
| + c.gridwidth = 2; |
| + c.insets = new Insets(5, 5, 5, 5); |
| + center.add(selectedTermTF, c); |
| + |
| + firstTermDocBtn.setText(FontUtils.elegantIconHtml("8", MessageUtils.getLocalizedMessage("documents.button.first_termdoc"))); |
| + firstTermDocBtn.addActionListener(listeners::showFirstTermDoc); |
| + c.gridx = 0; |
| + c.gridy = 2; |
| + c.weightx = 0.2; |
| + c.gridwidth = 1; |
| + c.insets = new Insets(5, 3, 5, 5); |
| + center.add(firstTermDocBtn, c); |
| + |
| + termDocIdxTF.setEditable(false); |
| + termDocIdxTF.setBackground(Color.white); |
| + c.gridx = 1; |
| + c.gridy = 2; |
| + c.weightx = 0.5; |
| + c.gridwidth = 1; |
| + c.insets = new Insets(5, 5, 5, 5); |
| + center.add(termDocIdxTF, c); |
| + |
| + nextTermDocBtn.setText(MessageUtils.getLocalizedMessage("documents.button.next")); |
| + nextTermDocBtn.addActionListener(listeners::showNextTermDoc); |
| + c.gridx = 2; |
| + c.gridy = 2; |
| + c.weightx = 0.2; |
| + c.gridwidth = 1; |
| + c.insets = new Insets(5, 5, 5, 5); |
| + center.add(nextTermDocBtn, c); |
| + |
| + termDocsNumLbl.setText("in ? docs"); |
| + c.gridx = 3; |
| + c.gridy = 2; |
| + c.weightx = 0.3; |
| + c.gridwidth = 1; |
| + c.insets = new Insets(5, 5, 5, 5); |
| + center.add(termDocsNumLbl, c); |
| + |
| + TableUtils.setupTable(posTable, ListSelectionModel.SINGLE_SELECTION, new PosTableModel(), null, |
| + PosTableModel.Column.POSITION.getColumnWidth(), PosTableModel.Column.OFFSETS.getColumnWidth(), PosTableModel.Column.PAYLOAD.getColumnWidth()); |
| + JScrollPane scrollPane = new JScrollPane(posTable); |
| + scrollPane.setMinimumSize(new Dimension(100, 100)); |
| + c.gridx = 0; |
| + c.gridy = 3; |
| + c.gridwidth = 4; |
| + c.insets = new Insets(5, 5, 5, 5); |
| + center.add(scrollPane, c); |
| + |
| + panel.add(center, BorderLayout.CENTER); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel initLowerPanel() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); |
| + |
| + JPanel browseDocsPanel = new JPanel(); |
| + browseDocsPanel.setOpaque(false); |
| + browseDocsPanel.setLayout(new BoxLayout(browseDocsPanel, BoxLayout.PAGE_AXIS)); |
| + browseDocsPanel.add(initBrowseDocsBar()); |
| + |
| + JPanel browseDocsNote1 = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + browseDocsNote1.setOpaque(false); |
| + browseDocsNote1.add(new JLabel(MessageUtils.getLocalizedMessage("documents.label.doc_table_note1"))); |
| + browseDocsPanel.add(browseDocsNote1); |
| + |
| + JPanel browseDocsNote2 = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + browseDocsNote2.setOpaque(false); |
| + browseDocsNote2.add(new JLabel(MessageUtils.getLocalizedMessage("documents.label.doc_table_note2"))); |
| + browseDocsPanel.add(browseDocsNote2); |
| + |
| + panel.add(browseDocsPanel, BorderLayout.PAGE_START); |
| + |
| + TableUtils.setupTable(documentTable, ListSelectionModel.MULTIPLE_INTERVAL_SELECTION, new DocumentsTableModel(), new MouseAdapter() { |
| + @Override |
| + public void mouseClicked(MouseEvent e) { |
| + listeners.showDocumentContextMenu(e); |
| + } |
| + }, |
| + DocumentsTableModel.Column.FIELD.getColumnWidth(), |
| + DocumentsTableModel.Column.FLAGS.getColumnWidth(), |
| + DocumentsTableModel.Column.NORM.getColumnWidth(), |
| + DocumentsTableModel.Column.VALUE.getColumnWidth()); |
| + JPanel flagsHeader = new JPanel(new FlowLayout(FlowLayout.CENTER)); |
| + flagsHeader.setOpaque(false); |
| + flagsHeader.add(new JLabel("Flags")); |
| + flagsHeader.add(new JLabel("Help")); |
| + documentTable.getColumnModel().getColumn(DocumentsTableModel.Column.FLAGS.getIndex()).setHeaderValue(flagsHeader); |
| + |
| + JScrollPane scrollPane = new JScrollPane(documentTable); |
| + scrollPane.getHorizontalScrollBar().setAutoscrolls(false); |
| + panel.add(scrollPane, BorderLayout.CENTER); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel initBrowseDocsBar() { |
| + JPanel panel = new JPanel(new GridLayout(1, 2)); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 5)); |
| + |
| + JPanel left = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 2)); |
| + left.setOpaque(false); |
| + JLabel label = new JLabel(FontUtils.elegantIconHtml("h", MessageUtils.getLocalizedMessage("documents.label.browse_doc_by_idx"))); |
| + label.setHorizontalTextPosition(JLabel.LEFT); |
| + left.add(label); |
| + docNumSpnr.setPreferredSize(new Dimension(100, 25)); |
| + docNumSpnr.addChangeListener(listeners::showCurrentDoc); |
| + left.add(docNumSpnr); |
| + maxDocsLbl.setText("in ? docs"); |
| + left.add(maxDocsLbl); |
| + panel.add(left); |
| + |
| + JPanel right = new JPanel(new FlowLayout(FlowLayout.TRAILING)); |
| + right.setOpaque(false); |
| + copyDocValuesBtn.setText(FontUtils.elegantIconHtml("", MessageUtils.getLocalizedMessage("documents.buttont.copy_values"))); |
| + copyDocValuesBtn.setMargin(new Insets(5, 0, 5, 0)); |
| + copyDocValuesBtn.addActionListener(listeners::copySelectedOrAllStoredValues); |
| + right.add(copyDocValuesBtn); |
| + mltBtn.setText(FontUtils.elegantIconHtml("", MessageUtils.getLocalizedMessage("documents.button.mlt"))); |
| + mltBtn.setMargin(new Insets(5, 0, 5, 0)); |
| + mltBtn.addActionListener(listeners::mltSearch); |
| + right.add(mltBtn); |
| + addDocBtn.setText(FontUtils.elegantIconHtml("Y", MessageUtils.getLocalizedMessage("documents.button.add"))); |
| + addDocBtn.setMargin(new Insets(5, 0, 5, 0)); |
| + addDocBtn.addActionListener(listeners::showAddDocumentDialog); |
| + right.add(addDocBtn); |
| + panel.add(right); |
| + |
| + return panel; |
| + } |
| + |
| + private void setUpDocumentContextMenu() { |
| + // show term vector |
| + JMenuItem item1 = new JMenuItem(MessageUtils.getLocalizedMessage("documents.doctable.menu.item1")); |
| + item1.addActionListener(listeners::showTermVectorDialog); |
| + documentContextMenu.add(item1); |
| + |
| + // show doc values |
| + JMenuItem item2 = new JMenuItem(MessageUtils.getLocalizedMessage("documents.doctable.menu.item2")); |
| + item2.addActionListener(listeners::showDocValuesDialog); |
| + documentContextMenu.add(item2); |
| + |
| + // show stored value |
| + JMenuItem item3 = new JMenuItem(MessageUtils.getLocalizedMessage("documents.doctable.menu.item3")); |
| + item3.addActionListener(listeners::showStoredValueDialog); |
| + documentContextMenu.add(item3); |
| + |
| + // copy stored value to clipboard |
| + JMenuItem item4 = new JMenuItem(MessageUtils.getLocalizedMessage("documents.doctable.menu.item4")); |
| + item4.addActionListener(listeners::copyStoredValue); |
| + documentContextMenu.add(item4); |
| + } |
| + |
| + // control methods |
| + |
| + private void showFirstTerm() { |
| + String fieldName = (String) fieldsCombo.getSelectedItem(); |
| + if (fieldName == null || fieldName.length() == 0) { |
| + messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("documents.field.message.not_selected")); |
| + return; |
| + } |
| + |
| + termDocIdxTF.setText(""); |
| + clearPosTable(); |
| + |
| + Optional<Term> firstTerm = documentsModel.firstTerm(fieldName); |
| + String firstTermText = firstTerm.map(Term::text).orElse(""); |
| + termTF.setText(firstTermText); |
| + selectedTermTF.setText(firstTermText); |
| + if (firstTerm.isPresent()) { |
| + String num = documentsModel.getDocFreq().map(String::valueOf).orElse("?"); |
| + termDocsNumLbl.setText("in " + num + " docs"); |
| + |
| + nextTermBtn.setEnabled(true); |
| + termTF.setEditable(true); |
| + firstTermDocBtn.setEnabled(true); |
| + } else { |
| + nextTermBtn.setEnabled(false); |
| + termTF.setEditable(false); |
| + firstTermDocBtn.setEnabled(false); |
| + } |
| + nextTermDocBtn.setEnabled(false); |
| + messageBroker.clearStatusMessage(); |
| + } |
| + |
| + private void showNextTerm() { |
| + termDocIdxTF.setText(""); |
| + clearPosTable(); |
| + |
| + Optional<Term> nextTerm = documentsModel.nextTerm(); |
| + String nextTermText = nextTerm.map(Term::text).orElse(""); |
| + termTF.setText(nextTermText); |
| + selectedTermTF.setText(nextTermText); |
| + if (nextTerm.isPresent()) { |
| + String num = documentsModel.getDocFreq().map(String::valueOf).orElse("?"); |
| + termDocsNumLbl.setText("in " + num + " docs"); |
| + |
| + termTF.setEditable(true); |
| + firstTermDocBtn.setEnabled(true); |
| + } else { |
| + nextTermBtn.setEnabled(false); |
| + termTF.setEditable(false); |
| + firstTermDocBtn.setEnabled(false); |
| + } |
| + nextTermDocBtn.setEnabled(false); |
| + messageBroker.clearStatusMessage(); |
| + } |
| + |
| + @Override |
| + public void seekNextTerm() { |
| + termDocIdxTF.setText(""); |
| + posTable.setModel(new PosTableModel()); |
| + |
| + String termText = termTF.getText(); |
| + |
| + Optional<Term> nextTerm = documentsModel.seekTerm(termText); |
| + String nextTermText = nextTerm.map(Term::text).orElse(""); |
| + termTF.setText(nextTermText); |
| + selectedTermTF.setText(nextTermText); |
| + if (nextTerm.isPresent()) { |
| + String num = documentsModel.getDocFreq().map(String::valueOf).orElse("?"); |
| + termDocsNumLbl.setText("in " + num + " docs"); |
| + |
| + termTF.setEditable(true); |
| + firstTermDocBtn.setEnabled(true); |
| + } else { |
| + nextTermBtn.setEnabled(false); |
| + termTF.setEditable(false); |
| + firstTermDocBtn.setEnabled(false); |
| + } |
| + nextTermDocBtn.setEnabled(false); |
| + messageBroker.clearStatusMessage(); |
| + } |
| + |
| + |
| + private void clearPosTable() { |
| + TableUtils.setupTable(posTable, ListSelectionModel.SINGLE_SELECTION, new PosTableModel(), null, |
| + PosTableModel.Column.POSITION.getColumnWidth(), |
| + PosTableModel.Column.OFFSETS.getColumnWidth(), |
| + PosTableModel.Column.PAYLOAD.getColumnWidth()); |
| + } |
| + |
| + @Override |
| + public void showFirstTermDoc() { |
| + int docid = documentsModel.firstTermDoc().orElse(-1); |
| + if (docid < 0) { |
| + nextTermDocBtn.setEnabled(false); |
| + messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("documents.termdocs.message.not_available")); |
| + return; |
| + } |
| + termDocIdxTF.setText(String.valueOf(1)); |
| + displayDoc(docid); |
| + |
| + List<TermPosting> postings = documentsModel.getTermPositions(); |
| + posTable.setModel(new PosTableModel(postings)); |
| + posTable.getColumnModel().getColumn(PosTableModel.Column.POSITION.getIndex()).setPreferredWidth(PosTableModel.Column.POSITION.getColumnWidth()); |
| + posTable.getColumnModel().getColumn(PosTableModel.Column.OFFSETS.getIndex()).setPreferredWidth(PosTableModel.Column.OFFSETS.getColumnWidth()); |
| + posTable.getColumnModel().getColumn(PosTableModel.Column.PAYLOAD.getIndex()).setPreferredWidth(PosTableModel.Column.PAYLOAD.getColumnWidth()); |
| + |
| + nextTermDocBtn.setEnabled(true); |
| + messageBroker.clearStatusMessage(); |
| + } |
| + |
| + private void showNextTermDoc() { |
| + int docid = documentsModel.nextTermDoc().orElse(-1); |
| + if (docid < 0) { |
| + nextTermDocBtn.setEnabled(false); |
| + messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("documents.termdocs.message.not_available")); |
| + return; |
| + } |
| + int curIdx = Integer.parseInt(termDocIdxTF.getText()); |
| + termDocIdxTF.setText(String.valueOf(curIdx + 1)); |
| + displayDoc(docid); |
| + |
| + List<TermPosting> postings = documentsModel.getTermPositions(); |
| + posTable.setModel(new PosTableModel(postings)); |
| + |
| + nextTermDocBtn.setDefaultCapable(true); |
| + messageBroker.clearStatusMessage(); |
| + } |
| + |
| + private void showCurrentDoc() { |
| + int docid = (Integer) docNumSpnr.getValue(); |
| + displayDoc(docid); |
| + } |
| + |
| + private void mltSearch() { |
| + int docNum = (int) docNumSpnr.getValue(); |
| + operatorRegistry.get(SearchTabOperator.class).ifPresent(operator -> { |
| + operator.mltSearch(docNum); |
| + tabSwitcher.switchTab(TabbedPaneProvider.Tab.SEARCH); |
| + }); |
| + } |
| + |
| + private void showAddDocumentDialog() { |
| + new DialogOpener<>(addDocDialogFactory).open("Add document", 600, 500, |
| + (factory) -> { |
| + }); |
| + } |
| + |
| + private void showTermVectorDialog() { |
| + int docid = (Integer) docNumSpnr.getValue(); |
| + String field = (String) documentTable.getModel().getValueAt(documentTable.getSelectedRow(), DocumentsTableModel.Column.FIELD.getIndex()); |
| + List<TermVectorEntry> tvEntries = documentsModel.getTermVectors(docid, field); |
| + if (tvEntries.isEmpty()) { |
| + messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("documents.termvector.message.not_available", field, docid)); |
| + return; |
| + } |
| + |
| + new DialogOpener<>(tvDialogFactory).open( |
| + "Term Vector", 600, 400, |
| + (factory) -> { |
| + factory.setField(field); |
| + factory.setTvEntries(tvEntries); |
| + }); |
| + messageBroker.clearStatusMessage(); |
| + } |
| + |
| + private void showDocValuesDialog() { |
| + int docid = (Integer) docNumSpnr.getValue(); |
| + String field = (String) documentTable.getModel().getValueAt(documentTable.getSelectedRow(), DocumentsTableModel.Column.FIELD.getIndex()); |
| + Optional<DocValues> docValues = documentsModel.getDocValues(docid, field); |
| + if (docValues.isPresent()) { |
| + new DialogOpener<>(dvDialogFactory).open( |
| + "Doc Values", 400, 300, |
| + (factory) -> { |
| + factory.setValue(field, docValues.get()); |
| + }); |
| + messageBroker.clearStatusMessage(); |
| + } else { |
| + messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("documents.docvalues.message.not_available", field, docid)); |
| + } |
| + } |
| + |
| + private void showStoredValueDialog() { |
| + int docid = (Integer) docNumSpnr.getValue(); |
| + String field = (String) documentTable.getModel().getValueAt(documentTable.getSelectedRow(), DocumentsTableModel.Column.FIELD.getIndex()); |
| + String value = (String) documentTable.getModel().getValueAt(documentTable.getSelectedRow(), DocumentsTableModel.Column.VALUE.getIndex()); |
| + if (Objects.isNull(value)) { |
| + messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("documents.stored.message.not_availabe", field, docid)); |
| + return; |
| + } |
| + new DialogOpener<>(valueDialogFactory).open( |
| + "Stored Value", 400, 300, |
| + (factory) -> { |
| + factory.setField(field); |
| + factory.setValue(value); |
| + }); |
| + messageBroker.clearStatusMessage(); |
| + } |
| + |
| + private void copyStoredValue() { |
| + int docid = (Integer) docNumSpnr.getValue(); |
| + String field = (String) documentTable.getModel().getValueAt(documentTable.getSelectedRow(), DocumentsTableModel.Column.FIELD.getIndex()); |
| + String value = (String) documentTable.getModel().getValueAt(documentTable.getSelectedRow(), DocumentsTableModel.Column.VALUE.getIndex()); |
| + if (Objects.isNull(value)) { |
| + messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("documents.stored.message.not_availabe", field, docid)); |
| + return; |
| + } |
| + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); |
| + StringSelection selection = new StringSelection(value); |
| + clipboard.setContents(selection, null); |
| + messageBroker.clearStatusMessage(); |
| + } |
| + |
| + private void copySelectedOrAllStoredValues() { |
| + StringSelection selection; |
| + if (documentTable.getSelectedRowCount() == 0) { |
| + selection = copyAllValues(); |
| + } else { |
| + selection = copySelectedValues(); |
| + } |
| + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); |
| + clipboard.setContents(selection, null); |
| + messageBroker.clearStatusMessage(); |
| + } |
| + |
| + private StringSelection copyAllValues() { |
| + StringBuilder sb = new StringBuilder(); |
| + for (int i = 0; i < documentTable.getRowCount(); i++) { |
| + String value = (String) documentTable.getModel().getValueAt(i, DocumentsTableModel.Column.VALUE.getIndex()); |
| + if (Objects.nonNull(value)) { |
| + sb.append((i == 0) ? value : System.lineSeparator() + value); |
| + } |
| + } |
| + return new StringSelection(sb.toString()); |
| + } |
| + |
| + private StringSelection copySelectedValues() { |
| + StringBuilder sb = new StringBuilder(); |
| + boolean isFirst = true; |
| + for (int rowIndex : documentTable.getSelectedRows()) { |
| + String value = (String) documentTable.getModel().getValueAt(rowIndex, DocumentsTableModel.Column.VALUE.getIndex()); |
| + if (Objects.nonNull(value)) { |
| + sb.append(isFirst ? value : System.lineSeparator() + value); |
| + isFirst = false; |
| + } |
| + } |
| + return new StringSelection(sb.toString()); |
| + } |
| + |
| + @Override |
| + public void browseTerm(String field, String term) { |
| + fieldsCombo.setSelectedItem(field); |
| + termTF.setText(term); |
| + seekNextTerm(); |
| + showFirstTermDoc(); |
| + } |
| + |
| + @Override |
| + public void displayLatestDoc() { |
| + int docid = documentsModel.getMaxDoc() - 1; |
| + showDoc(docid); |
| + } |
| + |
| + @Override |
| + public void displayDoc(int docid) { |
| + showDoc(docid); |
| + } |
| + |
| + ; |
| + |
| + private void showDoc(int docid) { |
| + docNumSpnr.setValue(docid); |
| + |
| + List<DocumentField> doc = documentsModel.getDocumentFields(docid); |
| + documentTable.setModel(new DocumentsTableModel(doc)); |
| + documentTable.setFont(StyleConstants.FONT_MONOSPACE_LARGE); |
| + documentTable.getColumnModel().getColumn(DocumentsTableModel.Column.FIELD.getIndex()).setPreferredWidth(DocumentsTableModel.Column.FIELD.getColumnWidth()); |
| + documentTable.getColumnModel().getColumn(DocumentsTableModel.Column.FLAGS.getIndex()).setMinWidth(DocumentsTableModel.Column.FLAGS.getColumnWidth()); |
| + documentTable.getColumnModel().getColumn(DocumentsTableModel.Column.FLAGS.getIndex()).setMaxWidth(DocumentsTableModel.Column.FIELD.getColumnWidth()); |
| + documentTable.getColumnModel().getColumn(DocumentsTableModel.Column.NORM.getIndex()).setMinWidth(DocumentsTableModel.Column.NORM.getColumnWidth()); |
| + documentTable.getColumnModel().getColumn(DocumentsTableModel.Column.NORM.getIndex()).setMaxWidth(DocumentsTableModel.Column.NORM.getColumnWidth()); |
| + documentTable.getColumnModel().getColumn(DocumentsTableModel.Column.VALUE.getIndex()).setPreferredWidth(DocumentsTableModel.Column.VALUE.getColumnWidth()); |
| + documentTable.getColumnModel().getColumn(DocumentsTableModel.Column.FLAGS.getIndex()).setHeaderRenderer(tableHeaderRenderer); |
| + |
| + messageBroker.clearStatusMessage(); |
| + } |
| + |
| + private class ListenerFunctions { |
| + |
| + void showFirstTerm(ActionEvent e) { |
| + DocumentsPanelProvider.this.showFirstTerm(); |
| + } |
| + |
| + void seekNextTerm(ActionEvent e) { |
| + DocumentsPanelProvider.this.seekNextTerm(); |
| + } |
| + |
| + void showNextTerm(ActionEvent e) { |
| + DocumentsPanelProvider.this.showNextTerm(); |
| + } |
| + |
| + void showFirstTermDoc(ActionEvent e) { |
| + DocumentsPanelProvider.this.showFirstTermDoc(); |
| + } |
| + |
| + void showNextTermDoc(ActionEvent e) { |
| + DocumentsPanelProvider.this.showNextTermDoc(); |
| + } |
| + |
| + void showCurrentDoc(ChangeEvent e) { |
| + DocumentsPanelProvider.this.showCurrentDoc(); |
| + } |
| + |
| + void mltSearch(ActionEvent e) { |
| + DocumentsPanelProvider.this.mltSearch(); |
| + } |
| + |
| + void showAddDocumentDialog(ActionEvent e) { |
| + DocumentsPanelProvider.this.showAddDocumentDialog(); |
| + } |
| + |
| + void showDocumentContextMenu(MouseEvent e) { |
| + if (e.getClickCount() == 2 && !e.isConsumed()) { |
| + int row = documentTable.rowAtPoint(e.getPoint()); |
| + if (row != documentTable.getSelectedRow()) { |
| + documentTable.changeSelection(row, documentTable.getSelectedColumn(), false, false); |
| + } |
| + documentContextMenu.show(e.getComponent(), e.getX(), e.getY()); |
| + } |
| + } |
| + |
| + void showTermVectorDialog(ActionEvent e) { |
| + DocumentsPanelProvider.this.showTermVectorDialog(); |
| + } |
| + |
| + void showDocValuesDialog(ActionEvent e) { |
| + DocumentsPanelProvider.this.showDocValuesDialog(); |
| + } |
| + |
| + void showStoredValueDialog(ActionEvent e) { |
| + DocumentsPanelProvider.this.showStoredValueDialog(); |
| + } |
| + |
| + void copyStoredValue(ActionEvent e) { |
| + DocumentsPanelProvider.this.copyStoredValue(); |
| + } |
| + |
| + void copySelectedOrAllStoredValues(ActionEvent e) { |
| + DocumentsPanelProvider.this.copySelectedOrAllStoredValues(); |
| + } |
| + |
| + } |
| + |
| + private class Observer implements IndexObserver { |
| + |
| + @Override |
| + public void openIndex(LukeState state) { |
| + documentsModel = documentsFactory.newInstance(state.getIndexReader()); |
| + |
| + addDocBtn.setEnabled(!state.readOnly() && state.hasDirectoryReader()); |
| + |
| + int maxDoc = documentsModel.getMaxDoc(); |
| + maxDocsLbl.setText("in " + maxDoc + " docs"); |
| + if (maxDoc > 0) { |
| + int max = Math.max(maxDoc - 1, 0); |
| + SpinnerModel spinnerModel = new SpinnerNumberModel(0, 0, max, 1); |
| + docNumSpnr.setModel(spinnerModel); |
| + docNumSpnr.setEnabled(true); |
| + displayDoc(0); |
| + } else { |
| + docNumSpnr.setEnabled(false); |
| + } |
| + |
| + documentsModel.getFieldNames().stream().sorted().forEach(fieldsCombo::addItem); |
| + } |
| + |
| + @Override |
| + public void closeIndex() { |
| + maxDocsLbl.setText("in ? docs"); |
| + docNumSpnr.setEnabled(false); |
| + fieldsCombo.removeAllItems(); |
| + termTF.setText(""); |
| + selectedTermTF.setText(""); |
| + termDocsNumLbl.setText(""); |
| + termDocIdxTF.setText(""); |
| + |
| + posTable.setModel(new PosTableModel()); |
| + documentTable.setModel(new DocumentsTableModel()); |
| + } |
| + } |
| + |
| + static final class PosTableModel extends TableModelBase<PosTableModel.Column> { |
| + |
| + enum Column implements TableColumnInfo { |
| + |
| + POSITION("Position", 0, Integer.class, 80), |
| + OFFSETS("Offsets", 1, String.class, 120), |
| + PAYLOAD("Payload", 2, String.class, 300); |
| + |
| + private final String colName; |
| + private final int index; |
| + private final Class<?> type; |
| + private final int width; |
| + |
| + Column(String colName, int index, Class<?> type, int width) { |
| + this.colName = colName; |
| + this.index = index; |
| + this.type = type; |
| + this.width = width; |
| + } |
| + |
| + @Override |
| + public String getColName() { |
| + return colName; |
| + } |
| + |
| + @Override |
| + public int getIndex() { |
| + return index; |
| + } |
| + |
| + @Override |
| + public Class<?> getType() { |
| + return type; |
| + } |
| + |
| + @Override |
| + public int getColumnWidth() { |
| + return width; |
| + } |
| + } |
| + |
| + PosTableModel() { |
| + super(); |
| + } |
| + |
| + PosTableModel(List<TermPosting> postings) { |
| + super(postings.size()); |
| + |
| + for (int i = 0; i < postings.size(); i++) { |
| + TermPosting p = postings.get(i); |
| + |
| + int position = postings.get(i).getPosition(); |
| + String offset = null; |
| + if (p.getStartOffset() >= 0 && p.getEndOffset() >= 0) { |
| + offset = p.getStartOffset() + "-" + p.getEndOffset(); |
| + } |
| + String payload = null; |
| + if (p.getPayload() != null) { |
| + payload = BytesRefUtils.decode(p.getPayload()); |
| + } |
| + |
| + data[i] = new Object[]{position, offset, payload}; |
| + } |
| + } |
| + |
| + @Override |
| + protected Column[] columnInfos() { |
| + return Column.values(); |
| + } |
| + } |
| + |
| + static final class DocumentsTableModel extends TableModelBase<DocumentsTableModel.Column> { |
| + |
| + enum Column implements TableColumnInfo { |
| + FIELD("Field", 0, String.class, 150), |
| + FLAGS("Flags", 1, String.class, 200), |
| + NORM("Norm", 2, Long.class, 80), |
| + VALUE("Value", 3, String.class, 500); |
| + |
| + private final String colName; |
| + private final int index; |
| + private final Class<?> type; |
| + private final int width; |
| + |
| + Column(String colName, int index, Class<?> type, int width) { |
| + this.colName = colName; |
| + this.index = index; |
| + this.type = type; |
| + this.width = width; |
| + } |
| + |
| + @Override |
| + public String getColName() { |
| + return colName; |
| + } |
| + |
| + @Override |
| + public int getIndex() { |
| + return index; |
| + } |
| + |
| + @Override |
| + public Class<?> getType() { |
| + return type; |
| + } |
| + |
| + @Override |
| + public int getColumnWidth() { |
| + return width; |
| + } |
| + } |
| + |
| + DocumentsTableModel() { |
| + super(); |
| + } |
| + |
| + DocumentsTableModel(List<DocumentField> doc) { |
| + super(doc.size()); |
| + |
| + for (int i = 0; i < doc.size(); i++) { |
| + DocumentField docField = doc.get(i); |
| + String field = docField.getName(); |
| + String flags = flags(docField); |
| + long norm = docField.getNorm(); |
| + String value = null; |
| + if (docField.getStringValue() != null) { |
| + value = docField.getStringValue(); |
| + } else if (docField.getNumericValue() != null) { |
| + value = String.valueOf(docField.getNumericValue()); |
| + } else if (docField.getBinaryValue() != null) { |
| + value = String.valueOf(docField.getBinaryValue()); |
| + } |
| + data[i] = new Object[]{field, flags, norm, value}; |
| + } |
| + } |
| + |
| + private static String flags(org.apache.lucene.luke.models.documents.DocumentField f) { |
| + StringBuilder sb = new StringBuilder(); |
| + // index options |
| + if (f.getIdxOptions() == null || f.getIdxOptions() == IndexOptions.NONE) { |
| + sb.append("-----"); |
| + } else { |
| + sb.append("I"); |
| + switch (f.getIdxOptions()) { |
| + case DOCS: |
| + sb.append("d---"); |
| + break; |
| + case DOCS_AND_FREQS: |
| + sb.append("df--"); |
| + break; |
| + case DOCS_AND_FREQS_AND_POSITIONS: |
| + sb.append("dfp-"); |
| + break; |
| + case DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS: |
| + sb.append("dfpo"); |
| + break; |
| + default: |
| + sb.append("----"); |
| + } |
| + } |
| + // has norm? |
| + if (f.hasNorms()) { |
| + sb.append("N"); |
| + } else { |
| + sb.append("-"); |
| + } |
| + // has payloads? |
| + if (f.hasPayloads()) { |
| + sb.append("P"); |
| + } else { |
| + sb.append("-"); |
| + } |
| + // stored? |
| + if (f.isStored()) { |
| + sb.append("S"); |
| + } else { |
| + sb.append("-"); |
| + } |
| + // binary? |
| + if (f.getBinaryValue() != null) { |
| + sb.append("B"); |
| + } else { |
| + sb.append("-"); |
| + } |
| + // numeric? |
| + if (f.getNumericValue() == null) { |
| + sb.append("----"); |
| + } else { |
| + sb.append("#"); |
| + // try faking it |
| + Number numeric = f.getNumericValue(); |
| + if (numeric instanceof Integer) { |
| + sb.append("i32"); |
| + } else if (numeric instanceof Long) { |
| + sb.append("i64"); |
| + } else if (numeric instanceof Float) { |
| + sb.append("f32"); |
| + } else if (numeric instanceof Double) { |
| + sb.append("f64"); |
| + } else if (numeric instanceof Short) { |
| + sb.append("i16"); |
| + } else if (numeric instanceof Byte) { |
| + sb.append("i08"); |
| + } else if (numeric instanceof BigDecimal) { |
| + sb.append("b^d"); |
| + } else if (numeric instanceof BigInteger) { |
| + sb.append("b^i"); |
| + } else { |
| + sb.append("???"); |
| + } |
| + } |
| + // has term vector? |
| + if (f.hasTermVectors()) { |
| + sb.append("V"); |
| + } else { |
| + sb.append("-"); |
| + } |
| + // doc values |
| + if (f.getDvType() == null || f.getDvType() == DocValuesType.NONE) { |
| + sb.append("-------"); |
| + } else { |
| + sb.append("D"); |
| + switch (f.getDvType()) { |
| + case NUMERIC: |
| + sb.append("number"); |
| + break; |
| + case BINARY: |
| + sb.append("binary"); |
| + break; |
| + case SORTED: |
| + sb.append("sorted"); |
| + break; |
| + case SORTED_NUMERIC: |
| + sb.append("srtnum"); |
| + break; |
| + case SORTED_SET: |
| + sb.append("srtset"); |
| + break; |
| + default: |
| + sb.append("??????"); |
| + } |
| + } |
| + // point values |
| + if (f.getPointDimensionCount() == 0) { |
| + sb.append("----"); |
| + } else { |
| + sb.append("T"); |
| + sb.append(f.getPointNumBytes()); |
| + sb.append("/"); |
| + sb.append(f.getPointDimensionCount()); |
| + } |
| + return sb.toString(); |
| + } |
| + |
| + @Override |
| + protected Column[] columnInfos() { |
| + return Column.values(); |
| + } |
| + } |
| + |
| +} |
| + |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/DocumentsTabOperator.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/DocumentsTabOperator.java |
| new file mode 100644 |
| index 00000000000..a0618da1f0a |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/DocumentsTabOperator.java |
| @@ -0,0 +1,31 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components; |
| + |
| +/** Operator for the Documents tab */ |
| +public interface DocumentsTabOperator extends ComponentOperatorRegistry.ComponentOperator { |
| + void browseTerm(String field, String term); |
| + |
| + void displayLatestDoc(); |
| + |
| + void displayDoc(int donid); |
| + |
| + void seekNextTerm(); |
| + |
| + void showFirstTermDoc(); |
| +} |
| \ No newline at end of file |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LogsPanelProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LogsPanelProvider.java |
| new file mode 100644 |
| index 00000000000..1d27cea9ff3 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LogsPanelProvider.java |
| @@ -0,0 +1,58 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components; |
| + |
| +import javax.swing.BorderFactory; |
| +import javax.swing.JLabel; |
| +import javax.swing.JPanel; |
| +import javax.swing.JScrollPane; |
| +import javax.swing.JTextArea; |
| +import java.awt.BorderLayout; |
| +import java.awt.FlowLayout; |
| + |
| +import org.apache.lucene.luke.app.desktop.LukeMain; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| + |
| +/** Provider of the Logs panel */ |
| +public final class LogsPanelProvider { |
| + |
| + private final JTextArea logTextArea; |
| + |
| + public LogsPanelProvider(JTextArea logTextArea) { |
| + this.logTextArea = logTextArea; |
| + } |
| + |
| + public JPanel get() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); |
| + |
| + JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + header.setOpaque(false); |
| + header.add(new JLabel(MessageUtils.getLocalizedMessage("logs.label.see_also"))); |
| + |
| + JLabel logPathLabel = new JLabel(LukeMain.LOG_FILE); |
| + header.add(logPathLabel); |
| + |
| + panel.add(header, BorderLayout.PAGE_START); |
| + |
| + panel.add(new JScrollPane(logTextArea), BorderLayout.CENTER); |
| + return panel; |
| + } |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LukeWindowOperator.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LukeWindowOperator.java |
| new file mode 100644 |
| index 00000000000..ecc51c88140 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LukeWindowOperator.java |
| @@ -0,0 +1,25 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components; |
| + |
| +import org.apache.lucene.luke.app.desktop.Preferences; |
| + |
| +/** Operator for the root window */ |
| +public interface LukeWindowOperator extends ComponentOperatorRegistry.ComponentOperator { |
| + void setColorTheme(Preferences.ColorTheme theme); |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LukeWindowProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LukeWindowProvider.java |
| new file mode 100644 |
| index 00000000000..faf5c1c1e27 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LukeWindowProvider.java |
| @@ -0,0 +1,250 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components; |
| + |
| +import javax.swing.BorderFactory; |
| +import javax.swing.JFrame; |
| +import javax.swing.JLabel; |
| +import javax.swing.JMenuBar; |
| +import javax.swing.JPanel; |
| +import javax.swing.JTabbedPane; |
| +import javax.swing.JTextArea; |
| +import javax.swing.WindowConstants; |
| +import java.awt.BorderLayout; |
| +import java.awt.Color; |
| +import java.awt.Dimension; |
| +import java.awt.FlowLayout; |
| +import java.awt.GridBagConstraints; |
| +import java.awt.GridBagLayout; |
| +import java.awt.GridLayout; |
| +import java.io.IOException; |
| + |
| +import org.apache.lucene.luke.app.DirectoryHandler; |
| +import org.apache.lucene.luke.app.DirectoryObserver; |
| +import org.apache.lucene.luke.app.IndexHandler; |
| +import org.apache.lucene.luke.app.IndexObserver; |
| +import org.apache.lucene.luke.app.LukeState; |
| +import org.apache.lucene.luke.app.desktop.MessageBroker; |
| +import org.apache.lucene.luke.app.desktop.Preferences; |
| +import org.apache.lucene.luke.app.desktop.PreferencesFactory; |
| +import org.apache.lucene.luke.app.desktop.util.FontUtils; |
| +import org.apache.lucene.luke.app.desktop.util.ImageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.TextAreaAppender; |
| +import org.apache.lucene.util.Version; |
| + |
| +/** Provider of the root window */ |
| +public final class LukeWindowProvider implements LukeWindowOperator { |
| + |
| + private static final String WINDOW_TITLE = MessageUtils.getLocalizedMessage("window.title") + " - v" + Version.LATEST.toString(); |
| + |
| + private final Preferences prefs; |
| + |
| + private final MessageBroker messageBroker; |
| + |
| + private final TabSwitcherProxy tabSwitcher; |
| + |
| + private final JMenuBar menuBar; |
| + |
| + private final JTabbedPane tabbedPane; |
| + |
| + private final JLabel messageLbl = new JLabel(); |
| + |
| + private final JLabel multiIcon = new JLabel(); |
| + |
| + private final JLabel readOnlyIcon = new JLabel(); |
| + |
| + private final JLabel noReaderIcon = new JLabel(); |
| + |
| + private JFrame frame = new JFrame(); |
| + |
| + public LukeWindowProvider() throws IOException { |
| + // prepare log4j appender for Logs tab. |
| + JTextArea logTextArea = new JTextArea(); |
| + logTextArea.setEditable(false); |
| + TextAreaAppender.setTextArea(logTextArea); |
| + |
| + this.prefs = PreferencesFactory.getInstance(); |
| + this.menuBar = new MenuBarProvider().get(); |
| + this.tabbedPane = new TabbedPaneProvider(logTextArea).get(); |
| + this.messageBroker = MessageBroker.getInstance(); |
| + this.tabSwitcher = TabSwitcherProxy.getInstance(); |
| + |
| + ComponentOperatorRegistry.getInstance().register(LukeWindowOperator.class, this); |
| + Observer observer = new Observer(); |
| + DirectoryHandler.getInstance().addObserver(observer); |
| + IndexHandler.getInstance().addObserver(observer); |
| + |
| + messageBroker.registerReceiver(new MessageReceiverImpl()); |
| + } |
| + |
| + public JFrame get() { |
| + frame.setTitle(WINDOW_TITLE); |
| + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); |
| + |
| + frame.setJMenuBar(menuBar); |
| + frame.add(initMainPanel(), BorderLayout.CENTER); |
| + frame.add(initMessagePanel(), BorderLayout.PAGE_END); |
| + |
| + frame.setPreferredSize(new Dimension(950, 680)); |
| + frame.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor()); |
| + |
| + return frame; |
| + } |
| + |
| + private JPanel initMainPanel() { |
| + JPanel panel = new JPanel(new GridLayout(1, 1)); |
| + |
| + tabbedPane.setEnabledAt(TabbedPaneProvider.Tab.OVERVIEW.index(), false); |
| + tabbedPane.setEnabledAt(TabbedPaneProvider.Tab.DOCUMENTS.index(), false); |
| + tabbedPane.setEnabledAt(TabbedPaneProvider.Tab.SEARCH.index(), false); |
| + tabbedPane.setEnabledAt(TabbedPaneProvider.Tab.COMMITS.index(), false); |
| + |
| + panel.add(tabbedPane); |
| + |
| + panel.setOpaque(false); |
| + return panel; |
| + } |
| + |
| + private JPanel initMessagePanel() { |
| + JPanel panel = new JPanel(new GridLayout(1, 1)); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(0, 2, 2, 2)); |
| + |
| + JPanel innerPanel = new JPanel(new GridBagLayout()); |
| + innerPanel.setOpaque(false); |
| + innerPanel.setBorder(BorderFactory.createLineBorder(Color.gray)); |
| + GridBagConstraints c = new GridBagConstraints(); |
| + c.fill = GridBagConstraints.HORIZONTAL; |
| + |
| + JPanel msgPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); |
| + msgPanel.setOpaque(false); |
| + msgPanel.add(messageLbl); |
| + |
| + c.gridx = 0; |
| + c.gridy = 0; |
| + c.weightx = 0.8; |
| + innerPanel.add(msgPanel, c); |
| + |
| + JPanel iconPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); |
| + iconPanel.setOpaque(false); |
| + |
| + multiIcon.setText(FontUtils.elegantIconHtml("")); |
| + multiIcon.setToolTipText(MessageUtils.getLocalizedMessage("tooltip.multi_reader")); |
| + multiIcon.setVisible(false); |
| + iconPanel.add(multiIcon); |
| + |
| + |
| + readOnlyIcon.setText(FontUtils.elegantIconHtml("")); |
| + readOnlyIcon.setToolTipText(MessageUtils.getLocalizedMessage("tooltip.read_only")); |
| + readOnlyIcon.setVisible(false); |
| + iconPanel.add(readOnlyIcon); |
| + |
| + noReaderIcon.setText(FontUtils.elegantIconHtml("")); |
| + noReaderIcon.setToolTipText(MessageUtils.getLocalizedMessage("tooltip.no_reader")); |
| + noReaderIcon.setVisible(false); |
| + iconPanel.add(noReaderIcon); |
| + |
| + JLabel luceneIcon = new JLabel(ImageUtils.createImageIcon("lucene.gif", "lucene", 16, 16)); |
| + iconPanel.add(luceneIcon); |
| + |
| + c.gridx = 1; |
| + c.gridy = 0; |
| + c.weightx = 0.2; |
| + innerPanel.add(iconPanel); |
| + panel.add(innerPanel); |
| + |
| + return panel; |
| + } |
| + |
| + @Override |
| + public void setColorTheme(Preferences.ColorTheme theme) { |
| + frame.getContentPane().setBackground(theme.getBackgroundColor()); |
| + } |
| + |
| + private class Observer implements IndexObserver, DirectoryObserver { |
| + |
| + @Override |
| + public void openDirectory(LukeState state) { |
| + multiIcon.setVisible(false); |
| + readOnlyIcon.setVisible(false); |
| + noReaderIcon.setVisible(true); |
| + |
| + tabSwitcher.switchTab(TabbedPaneProvider.Tab.COMMITS); |
| + |
| + messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("message.directory_opened")); |
| + } |
| + |
| + @Override |
| + public void closeDirectory() { |
| + multiIcon.setVisible(false); |
| + readOnlyIcon.setVisible(false); |
| + noReaderIcon.setVisible(false); |
| + |
| + messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("message.directory_closed")); |
| + } |
| + |
| + @Override |
| + public void openIndex(LukeState state) { |
| + multiIcon.setVisible(!state.hasDirectoryReader()); |
| + readOnlyIcon.setVisible(state.readOnly()); |
| + noReaderIcon.setVisible(false); |
| + |
| + if (state.readOnly()) { |
| + messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("message.index_opened_ro")); |
| + } else if (!state.hasDirectoryReader()) { |
| + messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("message.index_opened_multi")); |
| + } else { |
| + messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("message.index_opened")); |
| + } |
| + } |
| + |
| + @Override |
| + public void closeIndex() { |
| + multiIcon.setVisible(false); |
| + readOnlyIcon.setVisible(false); |
| + noReaderIcon.setVisible(false); |
| + |
| + messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("message.index_closed")); |
| + } |
| + |
| + } |
| + |
| + private class MessageReceiverImpl implements MessageBroker.MessageReceiver { |
| + |
| + @Override |
| + public void showStatusMessage(String message) { |
| + messageLbl.setText(message); |
| + } |
| + |
| + @Override |
| + public void showUnknownErrorMessage() { |
| + messageLbl.setText(MessageUtils.getLocalizedMessage("message.error.unknown")); |
| + } |
| + |
| + @Override |
| + public void clearStatusMessage() { |
| + messageLbl.setText(""); |
| + } |
| + |
| + private MessageReceiverImpl() { |
| + } |
| + |
| + } |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/MenuBarProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/MenuBarProvider.java |
| new file mode 100644 |
| index 00000000000..2a5008f4c2b |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/MenuBarProvider.java |
| @@ -0,0 +1,303 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components; |
| + |
| +import javax.swing.JMenu; |
| +import javax.swing.JMenuBar; |
| +import javax.swing.JMenuItem; |
| +import java.awt.event.ActionEvent; |
| +import java.io.IOException; |
| + |
| +import org.apache.lucene.luke.app.DirectoryHandler; |
| +import org.apache.lucene.luke.app.DirectoryObserver; |
| +import org.apache.lucene.luke.app.IndexHandler; |
| +import org.apache.lucene.luke.app.IndexObserver; |
| +import org.apache.lucene.luke.app.LukeState; |
| +import org.apache.lucene.luke.app.desktop.Preferences; |
| +import org.apache.lucene.luke.app.desktop.PreferencesFactory; |
| +import org.apache.lucene.luke.app.desktop.components.dialog.menubar.AboutDialogFactory; |
| +import org.apache.lucene.luke.app.desktop.components.dialog.menubar.CheckIndexDialogFactory; |
| +import org.apache.lucene.luke.app.desktop.components.dialog.menubar.CreateIndexDialogFactory; |
| +import org.apache.lucene.luke.app.desktop.components.dialog.menubar.OpenIndexDialogFactory; |
| +import org.apache.lucene.luke.app.desktop.components.dialog.menubar.OptimizeIndexDialogFactory; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.models.LukeException; |
| +import org.apache.lucene.util.Version; |
| + |
| +/** Provider of the MenuBar */ |
| +public final class MenuBarProvider { |
| + |
| + private final Preferences prefs; |
| + |
| + private final ComponentOperatorRegistry operatorRegistry; |
| + |
| + private final DirectoryHandler directoryHandler; |
| + |
| + private final IndexHandler indexHandler; |
| + |
| + private final OpenIndexDialogFactory openIndexDialogFactory; |
| + |
| + private final CreateIndexDialogFactory createIndexDialogFactory; |
| + |
| + private final OptimizeIndexDialogFactory optimizeIndexDialogFactory; |
| + |
| + private final CheckIndexDialogFactory checkIndexDialogFactory; |
| + |
| + private final AboutDialogFactory aboutDialogFactory; |
| + |
| + private final JMenuItem openIndexMItem = new JMenuItem(); |
| + |
| + private final JMenuItem reopenIndexMItem = new JMenuItem(); |
| + |
| + private final JMenuItem createIndexMItem = new JMenuItem(); |
| + |
| + private final JMenuItem closeIndexMItem = new JMenuItem(); |
| + |
| + private final JMenuItem grayThemeMItem = new JMenuItem(); |
| + |
| + private final JMenuItem classicThemeMItem = new JMenuItem(); |
| + |
| + private final JMenuItem sandstoneThemeMItem = new JMenuItem(); |
| + |
| + private final JMenuItem navyThemeMItem = new JMenuItem(); |
| + |
| + private final JMenuItem exitMItem = new JMenuItem(); |
| + |
| + private final JMenuItem optimizeIndexMItem = new JMenuItem(); |
| + |
| + private final JMenuItem checkIndexMItem = new JMenuItem(); |
| + |
| + private final JMenuItem aboutMItem = new JMenuItem(); |
| + |
| + private final ListenerFunctions listeners = new ListenerFunctions(); |
| + |
| + public MenuBarProvider() throws IOException { |
| + this.prefs = PreferencesFactory.getInstance(); |
| + this.directoryHandler = DirectoryHandler.getInstance(); |
| + this.indexHandler = IndexHandler.getInstance(); |
| + this.operatorRegistry = ComponentOperatorRegistry.getInstance(); |
| + this.openIndexDialogFactory = OpenIndexDialogFactory.getInstance(); |
| + this.createIndexDialogFactory = CreateIndexDialogFactory.getInstance(); |
| + this.optimizeIndexDialogFactory = OptimizeIndexDialogFactory.getInstance(); |
| + this.checkIndexDialogFactory = CheckIndexDialogFactory.getInstance(); |
| + this.aboutDialogFactory = AboutDialogFactory.getInstance(); |
| + |
| + Observer observer = new Observer(); |
| + directoryHandler.addObserver(observer); |
| + indexHandler.addObserver(observer); |
| + } |
| + |
| + public JMenuBar get() { |
| + JMenuBar menuBar = new JMenuBar(); |
| + |
| + menuBar.add(createFileMenu()); |
| + menuBar.add(createToolsMenu()); |
| + menuBar.add(createHelpMenu()); |
| + |
| + return menuBar; |
| + } |
| + |
| + private JMenu createFileMenu() { |
| + JMenu fileMenu = new JMenu(MessageUtils.getLocalizedMessage("menu.file")); |
| + |
| + openIndexMItem.setText(MessageUtils.getLocalizedMessage("menu.item.open_index")); |
| + openIndexMItem.addActionListener(listeners::showOpenIndexDialog); |
| + fileMenu.add(openIndexMItem); |
| + |
| + reopenIndexMItem.setText(MessageUtils.getLocalizedMessage("menu.item.reopen_index")); |
| + reopenIndexMItem.setEnabled(false); |
| + reopenIndexMItem.addActionListener(listeners::reopenIndex); |
| + fileMenu.add(reopenIndexMItem); |
| + |
| + createIndexMItem.setText(MessageUtils.getLocalizedMessage("menu.item.create_index")); |
| + createIndexMItem.addActionListener(listeners::showCreateIndexDialog); |
| + fileMenu.add(createIndexMItem); |
| + |
| + |
| + closeIndexMItem.setText(MessageUtils.getLocalizedMessage("menu.item.close_index")); |
| + closeIndexMItem.setEnabled(false); |
| + closeIndexMItem.addActionListener(listeners::closeIndex); |
| + fileMenu.add(closeIndexMItem); |
| + |
| + fileMenu.addSeparator(); |
| + |
| + JMenu settingsMenu = new JMenu(MessageUtils.getLocalizedMessage("menu.settings")); |
| + JMenu themeMenu = new JMenu(MessageUtils.getLocalizedMessage("menu.color")); |
| + grayThemeMItem.setText(MessageUtils.getLocalizedMessage("menu.item.theme_gray")); |
| + grayThemeMItem.addActionListener(listeners::changeThemeToGray); |
| + themeMenu.add(grayThemeMItem); |
| + classicThemeMItem.setText(MessageUtils.getLocalizedMessage("menu.item.theme_classic")); |
| + classicThemeMItem.addActionListener(listeners::changeThemeToClassic); |
| + themeMenu.add(classicThemeMItem); |
| + sandstoneThemeMItem.setText(MessageUtils.getLocalizedMessage("menu.item.theme_sandstone")); |
| + sandstoneThemeMItem.addActionListener(listeners::changeThemeToSandstone); |
| + themeMenu.add(sandstoneThemeMItem); |
| + navyThemeMItem.setText(MessageUtils.getLocalizedMessage("menu.item.theme_navy")); |
| + navyThemeMItem.addActionListener(listeners::changeThemeToNavy); |
| + themeMenu.add(navyThemeMItem); |
| + settingsMenu.add(themeMenu); |
| + fileMenu.add(settingsMenu); |
| + |
| + fileMenu.addSeparator(); |
| + |
| + exitMItem.setText(MessageUtils.getLocalizedMessage("menu.item.exit")); |
| + exitMItem.addActionListener(listeners::exit); |
| + fileMenu.add(exitMItem); |
| + |
| + return fileMenu; |
| + } |
| + |
| + private JMenu createToolsMenu() { |
| + JMenu toolsMenu = new JMenu(MessageUtils.getLocalizedMessage("menu.tools")); |
| + optimizeIndexMItem.setText(MessageUtils.getLocalizedMessage("menu.item.optimize")); |
| + optimizeIndexMItem.setEnabled(false); |
| + optimizeIndexMItem.addActionListener(listeners::showOptimizeIndexDialog); |
| + toolsMenu.add(optimizeIndexMItem); |
| + checkIndexMItem.setText(MessageUtils.getLocalizedMessage("menu.item.check_index")); |
| + checkIndexMItem.setEnabled(false); |
| + checkIndexMItem.addActionListener(listeners::showCheckIndexDialog); |
| + toolsMenu.add(checkIndexMItem); |
| + return toolsMenu; |
| + } |
| + |
| + private JMenu createHelpMenu() { |
| + JMenu helpMenu = new JMenu(MessageUtils.getLocalizedMessage("menu.help")); |
| + aboutMItem.setText(MessageUtils.getLocalizedMessage("menu.item.about")); |
| + aboutMItem.addActionListener(listeners::showAboutDialog); |
| + helpMenu.add(aboutMItem); |
| + return helpMenu; |
| + } |
| + |
| + private class ListenerFunctions { |
| + |
| + void showOpenIndexDialog(ActionEvent e) { |
| + new DialogOpener<>(openIndexDialogFactory).open(MessageUtils.getLocalizedMessage("openindex.dialog.title"), 600, 420, |
| + (factory) -> {}); |
| + } |
| + |
| + void showCreateIndexDialog(ActionEvent e) { |
| + new DialogOpener<>(createIndexDialogFactory).open(MessageUtils.getLocalizedMessage("createindex.dialog.title"), 600, 360, |
| + (factory) -> {}); |
| + } |
| + |
| + void reopenIndex(ActionEvent e) { |
| + indexHandler.reOpen(); |
| + } |
| + |
| + void closeIndex(ActionEvent e) { |
| + close(); |
| + } |
| + |
| + void changeThemeToGray(ActionEvent e) { |
| + changeTheme(Preferences.ColorTheme.GRAY); |
| + } |
| + |
| + void changeThemeToClassic(ActionEvent e) { |
| + changeTheme(Preferences.ColorTheme.CLASSIC); |
| + } |
| + |
| + void changeThemeToSandstone(ActionEvent e) { |
| + changeTheme(Preferences.ColorTheme.SANDSTONE); |
| + } |
| + |
| + void changeThemeToNavy(ActionEvent e) { |
| + changeTheme(Preferences.ColorTheme.NAVY); |
| + } |
| + |
| + private void changeTheme(Preferences.ColorTheme theme) { |
| + try { |
| + prefs.setColorTheme(theme); |
| + operatorRegistry.get(LukeWindowOperator.class).ifPresent(operator -> operator.setColorTheme(theme)); |
| + } catch (IOException e) { |
| + throw new LukeException("Failed to set color theme : " + theme.name(), e); |
| + } |
| + } |
| + |
| + void exit(ActionEvent e) { |
| + close(); |
| + System.exit(0); |
| + } |
| + |
| + private void close() { |
| + directoryHandler.close(); |
| + indexHandler.close(); |
| + } |
| + |
| + void showOptimizeIndexDialog(ActionEvent e) { |
| + new DialogOpener<>(optimizeIndexDialogFactory).open("Optimize index", 600, 600, |
| + factory -> { |
| + }); |
| + } |
| + |
| + void showCheckIndexDialog(ActionEvent e) { |
| + new DialogOpener<>(checkIndexDialogFactory).open("Check index", 600, 600, |
| + factory -> { |
| + }); |
| + } |
| + |
| + void showAboutDialog(ActionEvent e) { |
| + final String title = "About Luke v" + Version.LATEST.toString(); |
| + new DialogOpener<>(aboutDialogFactory).open(title, 800, 480, |
| + factory -> { |
| + }); |
| + } |
| + |
| + } |
| + |
| + private class Observer implements IndexObserver, DirectoryObserver { |
| + |
| + @Override |
| + public void openDirectory(LukeState state) { |
| + reopenIndexMItem.setEnabled(false); |
| + closeIndexMItem.setEnabled(false); |
| + optimizeIndexMItem.setEnabled(false); |
| + checkIndexMItem.setEnabled(true); |
| + } |
| + |
| + @Override |
| + public void closeDirectory() { |
| + close(); |
| + } |
| + |
| + @Override |
| + public void openIndex(LukeState state) { |
| + reopenIndexMItem.setEnabled(true); |
| + closeIndexMItem.setEnabled(true); |
| + if (!state.readOnly() && state.hasDirectoryReader()) { |
| + optimizeIndexMItem.setEnabled(true); |
| + } |
| + if (state.hasDirectoryReader()) { |
| + checkIndexMItem.setEnabled(true); |
| + } |
| + } |
| + |
| + @Override |
| + public void closeIndex() { |
| + close(); |
| + } |
| + |
| + private void close() { |
| + reopenIndexMItem.setEnabled(false); |
| + closeIndexMItem.setEnabled(false); |
| + optimizeIndexMItem.setEnabled(false); |
| + checkIndexMItem.setEnabled(false); |
| + } |
| + |
| + } |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/OverviewPanelProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/OverviewPanelProvider.java |
| new file mode 100644 |
| index 00000000000..c85e93bcd7c |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/OverviewPanelProvider.java |
| @@ -0,0 +1,644 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components; |
| + |
| +import javax.swing.BorderFactory; |
| +import javax.swing.BoxLayout; |
| +import javax.swing.JButton; |
| +import javax.swing.JLabel; |
| +import javax.swing.JMenuItem; |
| +import javax.swing.JPanel; |
| +import javax.swing.JPopupMenu; |
| +import javax.swing.JScrollPane; |
| +import javax.swing.JSpinner; |
| +import javax.swing.JSplitPane; |
| +import javax.swing.JTable; |
| +import javax.swing.JTextField; |
| +import javax.swing.ListSelectionModel; |
| +import javax.swing.SpinnerNumberModel; |
| +import javax.swing.table.DefaultTableCellRenderer; |
| +import javax.swing.table.TableRowSorter; |
| +import java.awt.BorderLayout; |
| +import java.awt.Color; |
| +import java.awt.Dimension; |
| +import java.awt.FlowLayout; |
| +import java.awt.GridBagConstraints; |
| +import java.awt.GridBagLayout; |
| +import java.awt.GridLayout; |
| +import java.awt.Insets; |
| +import java.awt.event.ActionEvent; |
| +import java.awt.event.MouseAdapter; |
| +import java.awt.event.MouseEvent; |
| +import java.util.List; |
| +import java.util.Locale; |
| +import java.util.Map; |
| + |
| +import org.apache.lucene.luke.app.IndexHandler; |
| +import org.apache.lucene.luke.app.IndexObserver; |
| +import org.apache.lucene.luke.app.LukeState; |
| +import org.apache.lucene.luke.app.desktop.MessageBroker; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.StyleConstants; |
| +import org.apache.lucene.luke.app.desktop.util.TableUtils; |
| +import org.apache.lucene.luke.models.overview.Overview; |
| +import org.apache.lucene.luke.models.overview.OverviewFactory; |
| +import org.apache.lucene.luke.models.overview.TermCountsOrder; |
| +import org.apache.lucene.luke.models.overview.TermStats; |
| + |
| +/** Provider of the Overview panel */ |
| +public final class OverviewPanelProvider { |
| + |
| + private static final int GRIDX_DESC = 0; |
| + private static final int GRIDX_VAL = 1; |
| + private static final double WEIGHTX_DESC = 0.1; |
| + private static final double WEIGHTX_VAL = 0.9; |
| + |
| + private final OverviewFactory overviewFactory = new OverviewFactory(); |
| + |
| + private final ComponentOperatorRegistry operatorRegistry; |
| + |
| + private final TabSwitcherProxy tabSwitcher; |
| + |
| + private final MessageBroker messageBroker; |
| + |
| + private final JPanel panel = new JPanel(); |
| + |
| + private final JLabel indexPathLbl = new JLabel(); |
| + |
| + private final JLabel numFieldsLbl = new JLabel(); |
| + |
| + private final JLabel numDocsLbl = new JLabel(); |
| + |
| + private final JLabel numTermsLbl = new JLabel(); |
| + |
| + private final JLabel delOptLbl = new JLabel(); |
| + |
| + private final JLabel indexVerLbl = new JLabel(); |
| + |
| + private final JLabel indexFmtLbl = new JLabel(); |
| + |
| + private final JLabel dirImplLbl = new JLabel(); |
| + |
| + private final JLabel commitPointLbl = new JLabel(); |
| + |
| + private final JLabel commitUserDataLbl = new JLabel(); |
| + |
| + private final JTable termCountsTable = new JTable(); |
| + |
| + private final JTextField selectedField = new JTextField(); |
| + |
| + private final JButton showTopTermsBtn = new JButton(); |
| + |
| + private final JSpinner numTopTermsSpnr = new JSpinner(); |
| + |
| + private final JTable topTermsTable = new JTable(); |
| + |
| + private final JPopupMenu topTermsContextMenu = new JPopupMenu(); |
| + |
| + private final ListenerFunctions listeners = new ListenerFunctions(); |
| + |
| + private Overview overviewModel; |
| + |
| + public OverviewPanelProvider() { |
| + this.messageBroker = MessageBroker.getInstance(); |
| + this.operatorRegistry = ComponentOperatorRegistry.getInstance(); |
| + this.tabSwitcher = TabSwitcherProxy.getInstance(); |
| + |
| + IndexHandler.getInstance().addObserver(new Observer()); |
| + } |
| + |
| + public JPanel get() { |
| + panel.setOpaque(false); |
| + panel.setLayout(new GridLayout(1, 1)); |
| + panel.setBorder(BorderFactory.createLineBorder(Color.gray)); |
| + |
| + JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, initUpperPanel(), initLowerPanel()); |
| + splitPane.setDividerLocation(0.4); |
| + splitPane.setOpaque(false); |
| + panel.add(splitPane); |
| + |
| + setUpTopTermsContextMenu(); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel initUpperPanel() { |
| + JPanel panel = new JPanel(new GridBagLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); |
| + |
| + GridBagConstraints c = new GridBagConstraints(); |
| + c.fill = GridBagConstraints.HORIZONTAL; |
| + c.insets = new Insets(2, 10, 2, 2); |
| + c.gridy = 0; |
| + |
| + c.gridx = GRIDX_DESC; |
| + c.weightx = WEIGHTX_DESC; |
| + panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.index_path"), JLabel.RIGHT), c); |
| + |
| + c.gridx = GRIDX_VAL; |
| + c.weightx = WEIGHTX_VAL; |
| + indexPathLbl.setText("?"); |
| + panel.add(indexPathLbl, c); |
| + |
| + c.gridx = GRIDX_DESC; |
| + c.gridy += 1; |
| + c.weightx = WEIGHTX_DESC; |
| + panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.num_fields"), JLabel.RIGHT), c); |
| + |
| + c.gridx = GRIDX_VAL; |
| + c.weightx = WEIGHTX_VAL; |
| + numFieldsLbl.setText("?"); |
| + panel.add(numFieldsLbl, c); |
| + |
| + c.gridx = GRIDX_DESC; |
| + c.gridy += 1; |
| + c.weightx = WEIGHTX_DESC; |
| + panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.num_docs"), JLabel.RIGHT), c); |
| + |
| + c.gridx = GRIDX_VAL; |
| + c.weightx = WEIGHTX_VAL; |
| + numDocsLbl.setText("?"); |
| + panel.add(numDocsLbl, c); |
| + |
| + c.gridx = GRIDX_DESC; |
| + c.gridy += 1; |
| + c.weightx = WEIGHTX_DESC; |
| + panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.num_terms"), JLabel.RIGHT), c); |
| + |
| + c.gridx = GRIDX_VAL; |
| + c.weightx = WEIGHTX_VAL; |
| + numTermsLbl.setText("?"); |
| + panel.add(numTermsLbl, c); |
| + |
| + c.gridx = GRIDX_DESC; |
| + c.gridy += 1; |
| + c.weightx = WEIGHTX_DESC; |
| + panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.del_opt"), JLabel.RIGHT), c); |
| + |
| + c.gridx = GRIDX_VAL; |
| + c.weightx = WEIGHTX_VAL; |
| + delOptLbl.setText("?"); |
| + panel.add(delOptLbl, c); |
| + |
| + c.gridx = GRIDX_DESC; |
| + c.gridy += 1; |
| + c.weightx = WEIGHTX_DESC; |
| + panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.index_version"), JLabel.RIGHT), c); |
| + |
| + c.gridx = GRIDX_VAL; |
| + c.weightx = WEIGHTX_VAL; |
| + indexVerLbl.setText("?"); |
| + panel.add(indexVerLbl, c); |
| + |
| + c.gridx = GRIDX_DESC; |
| + c.gridy += 1; |
| + c.weightx = WEIGHTX_DESC; |
| + panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.index_format"), JLabel.RIGHT), c); |
| + |
| + c.gridx = GRIDX_VAL; |
| + c.weightx = WEIGHTX_VAL; |
| + indexFmtLbl.setText("?"); |
| + panel.add(indexFmtLbl, c); |
| + |
| + c.gridx = GRIDX_DESC; |
| + c.gridy += 1; |
| + c.weightx = WEIGHTX_DESC; |
| + panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.dir_impl"), JLabel.RIGHT), c); |
| + |
| + c.gridx = GRIDX_VAL; |
| + c.weightx = WEIGHTX_VAL; |
| + dirImplLbl.setText("?"); |
| + panel.add(dirImplLbl, c); |
| + |
| + c.gridx = GRIDX_DESC; |
| + c.gridy += 1; |
| + c.weightx = WEIGHTX_DESC; |
| + panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.commit_point"), JLabel.RIGHT), c); |
| + |
| + c.gridx = GRIDX_VAL; |
| + c.weightx = WEIGHTX_VAL; |
| + commitPointLbl.setText("?"); |
| + panel.add(commitPointLbl, c); |
| + |
| + c.gridx = GRIDX_DESC; |
| + c.gridy += 1; |
| + c.weightx = WEIGHTX_DESC; |
| + panel.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.commit_userdata"), JLabel.RIGHT), c); |
| + |
| + c.gridx = GRIDX_VAL; |
| + c.weightx = WEIGHTX_VAL; |
| + commitUserDataLbl.setText("?"); |
| + panel.add(commitUserDataLbl, c); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel initLowerPanel() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + |
| + JLabel label = new JLabel(MessageUtils.getLocalizedMessage("overview.label.select_fields")); |
| + label.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10)); |
| + panel.add(label, BorderLayout.PAGE_START); |
| + |
| + JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, initTermCountsPanel(), initTopTermsPanel()); |
| + splitPane.setOpaque(false); |
| + splitPane.setDividerLocation(320); |
| + splitPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); |
| + panel.add(splitPane, BorderLayout.CENTER); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel initTermCountsPanel() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + |
| + JLabel label = new JLabel(MessageUtils.getLocalizedMessage("overview.label.available_fields")); |
| + label.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0)); |
| + panel.add(label, BorderLayout.PAGE_START); |
| + |
| + TableUtils.setupTable(termCountsTable, ListSelectionModel.SINGLE_SELECTION, new TermCountsTableModel(), |
| + new MouseAdapter() { |
| + @Override |
| + public void mouseClicked(MouseEvent e) { |
| + listeners.selectField(e); |
| + } |
| + }, TermCountsTableModel.Column.NAME.getColumnWidth(), TermCountsTableModel.Column.TERM_COUNT.getColumnWidth()); |
| + JScrollPane scrollPane = new JScrollPane(termCountsTable); |
| + panel.add(scrollPane, BorderLayout.CENTER); |
| + |
| + panel.setOpaque(false); |
| + return panel; |
| + } |
| + |
| + private JPanel initTopTermsPanel() { |
| + JPanel panel = new JPanel(new GridLayout(1, 1)); |
| + panel.setOpaque(false); |
| + |
| + JPanel selectedPanel = new JPanel(new BorderLayout()); |
| + selectedPanel.setOpaque(false); |
| + JPanel innerPanel = new JPanel(); |
| + innerPanel.setOpaque(false); |
| + innerPanel.setLayout(new BoxLayout(innerPanel, BoxLayout.PAGE_AXIS)); |
| + innerPanel.setBorder(BorderFactory.createEmptyBorder(20, 0, 0, 0)); |
| + selectedPanel.add(innerPanel, BorderLayout.PAGE_START); |
| + |
| + JPanel innerPanel1 = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + innerPanel1.setOpaque(false); |
| + innerPanel1.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.selected_field"))); |
| + innerPanel.add(innerPanel1); |
| + |
| + selectedField.setColumns(20); |
| + selectedField.setPreferredSize(new Dimension(100, 30)); |
| + selectedField.setFont(StyleConstants.FONT_MONOSPACE_LARGE); |
| + selectedField.setEditable(false); |
| + selectedField.setBackground(Color.white); |
| + JPanel innerPanel2 = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + innerPanel2.setOpaque(false); |
| + innerPanel2.add(selectedField); |
| + innerPanel.add(innerPanel2); |
| + |
| + showTopTermsBtn.setText(MessageUtils.getLocalizedMessage("overview.button.show_terms")); |
| + showTopTermsBtn.setPreferredSize(new Dimension(170, 40)); |
| + showTopTermsBtn.setFont(StyleConstants.FONT_BUTTON_LARGE); |
| + showTopTermsBtn.addActionListener(listeners::showTopTerms); |
| + showTopTermsBtn.setEnabled(false); |
| + JPanel innerPanel3 = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + innerPanel3.setOpaque(false); |
| + innerPanel3.add(showTopTermsBtn); |
| + innerPanel.add(innerPanel3); |
| + |
| + JPanel innerPanel4 = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + innerPanel4.setOpaque(false); |
| + innerPanel4.add(new JLabel(MessageUtils.getLocalizedMessage("overview.label.num_top_terms"))); |
| + innerPanel.add(innerPanel4); |
| + |
| + SpinnerNumberModel numberModel = new SpinnerNumberModel(50, 0, 1000, 1); |
| + numTopTermsSpnr.setPreferredSize(new Dimension(80, 30)); |
| + numTopTermsSpnr.setModel(numberModel); |
| + JPanel innerPanel5 = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + innerPanel5.setOpaque(false); |
| + innerPanel5.add(numTopTermsSpnr); |
| + innerPanel.add(innerPanel5); |
| + |
| + JPanel termsPanel = new JPanel(new BorderLayout()); |
| + termsPanel.setOpaque(false); |
| + JLabel label = new JLabel(MessageUtils.getLocalizedMessage("overview.label.top_terms")); |
| + label.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0)); |
| + termsPanel.add(label, BorderLayout.PAGE_START); |
| + |
| + TableUtils.setupTable(topTermsTable, ListSelectionModel.SINGLE_SELECTION, new TopTermsTableModel(), |
| + new MouseAdapter() { |
| + @Override |
| + public void mouseClicked(MouseEvent e) { |
| + listeners.showTopTermsContextMenu(e); |
| + } |
| + }, TopTermsTableModel.Column.RANK.getColumnWidth(), TopTermsTableModel.Column.FREQ.getColumnWidth()); |
| + JScrollPane scrollPane = new JScrollPane(topTermsTable); |
| + termsPanel.add(scrollPane, BorderLayout.CENTER); |
| + |
| + JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, selectedPanel, termsPanel); |
| + splitPane.setOpaque(false); |
| + splitPane.setDividerLocation(180); |
| + splitPane.setBorder(BorderFactory.createEmptyBorder()); |
| + panel.add(splitPane); |
| + |
| + return panel; |
| + } |
| + |
| + private void setUpTopTermsContextMenu() { |
| + JMenuItem item1 = new JMenuItem(MessageUtils.getLocalizedMessage("overview.toptermtable.menu.item1")); |
| + item1.addActionListener(listeners::browseByTerm); |
| + topTermsContextMenu.add(item1); |
| + |
| + JMenuItem item2 = new JMenuItem(MessageUtils.getLocalizedMessage("overview.toptermtable.menu.item2")); |
| + item2.addActionListener(listeners::searchByTerm); |
| + topTermsContextMenu.add(item2); |
| + } |
| + |
| + // control methods |
| + |
| + private void selectField() { |
| + String field = getSelectedField(); |
| + selectedField.setText(field); |
| + showTopTermsBtn.setEnabled(true); |
| + } |
| + |
| + private void showTopTerms() { |
| + String field = getSelectedField(); |
| + int numTerms = (int) numTopTermsSpnr.getModel().getValue(); |
| + List<TermStats> termStats = overviewModel.getTopTerms(field, numTerms); |
| + |
| + // update top terms table |
| + topTermsTable.setModel(new TopTermsTableModel(termStats, numTerms)); |
| + topTermsTable.getColumnModel().getColumn(TopTermsTableModel.Column.RANK.getIndex()).setMaxWidth(TopTermsTableModel.Column.RANK.getColumnWidth()); |
| + topTermsTable.getColumnModel().getColumn(TopTermsTableModel.Column.FREQ.getIndex()).setMaxWidth(TopTermsTableModel.Column.FREQ.getColumnWidth()); |
| + messageBroker.clearStatusMessage(); |
| + } |
| + |
| + private void browseByTerm() { |
| + String field = getSelectedField(); |
| + String term = getSelectedTerm(); |
| + operatorRegistry.get(DocumentsTabOperator.class).ifPresent(operator -> { |
| + operator.browseTerm(field, term); |
| + tabSwitcher.switchTab(TabbedPaneProvider.Tab.DOCUMENTS); |
| + }); |
| + } |
| + |
| + private void searchByTerm() { |
| + String field = getSelectedField(); |
| + String term = getSelectedTerm(); |
| + operatorRegistry.get(SearchTabOperator.class).ifPresent(operator -> { |
| + operator.searchByTerm(field, term); |
| + tabSwitcher.switchTab(TabbedPaneProvider.Tab.SEARCH); |
| + }); |
| + } |
| + |
| + private String getSelectedField() { |
| + int selected = termCountsTable.getSelectedRow(); |
| + // need to convert selected row index to underlying model index |
| + // https://docs.oracle.com/javase/8/docs/api/javax/swing/table/TableRowSorter.html |
| + int row = termCountsTable.convertRowIndexToModel(selected); |
| + if (row < 0 || row >= termCountsTable.getRowCount()) { |
| + throw new IllegalStateException("Field is not selected."); |
| + } |
| + return (String) termCountsTable.getModel().getValueAt(row, TermCountsTableModel.Column.NAME.getIndex()); |
| + } |
| + |
| + private String getSelectedTerm() { |
| + int rowTerm = topTermsTable.getSelectedRow(); |
| + if (rowTerm < 0 || rowTerm >= topTermsTable.getRowCount()) { |
| + throw new IllegalStateException("Term is not selected."); |
| + } |
| + return (String) topTermsTable.getModel().getValueAt(rowTerm, TopTermsTableModel.Column.TEXT.getIndex()); |
| + } |
| + |
| + private class ListenerFunctions { |
| + |
| + void selectField(MouseEvent e) { |
| + OverviewPanelProvider.this.selectField(); |
| + } |
| + |
| + void showTopTerms(ActionEvent e) { |
| + OverviewPanelProvider.this.showTopTerms(); |
| + } |
| + |
| + void showTopTermsContextMenu(MouseEvent e) { |
| + if (e.getClickCount() == 2 && !e.isConsumed()) { |
| + int row = topTermsTable.rowAtPoint(e.getPoint()); |
| + if (row != topTermsTable.getSelectedRow()) { |
| + topTermsTable.changeSelection(row, topTermsTable.getSelectedColumn(), false, false); |
| + } |
| + topTermsContextMenu.show(e.getComponent(), e.getX(), e.getY()); |
| + } |
| + } |
| + |
| + void browseByTerm(ActionEvent e) { |
| + OverviewPanelProvider.this.browseByTerm(); |
| + } |
| + |
| + void searchByTerm(ActionEvent e) { |
| + OverviewPanelProvider.this.searchByTerm(); |
| + } |
| + |
| + } |
| + |
| + private class Observer implements IndexObserver { |
| + |
| + @Override |
| + public void openIndex(LukeState state) { |
| + overviewModel = overviewFactory.newInstance(state.getIndexReader(), state.getIndexPath()); |
| + |
| + indexPathLbl.setText(overviewModel.getIndexPath()); |
| + indexPathLbl.setToolTipText(overviewModel.getIndexPath()); |
| + numFieldsLbl.setText(Integer.toString(overviewModel.getNumFields())); |
| + numDocsLbl.setText(Integer.toString(overviewModel.getNumDocuments())); |
| + numTermsLbl.setText(Long.toString(overviewModel.getNumTerms())); |
| + String del = overviewModel.hasDeletions() ? String.format(Locale.ENGLISH, "Yes (%d)", overviewModel.getNumDeletedDocs()) : "No"; |
| + String opt = overviewModel.isOptimized().map(b -> b ? "Yes" : "No").orElse("?"); |
| + delOptLbl.setText(del + " / " + opt); |
| + indexVerLbl.setText(overviewModel.getIndexVersion().map(v -> Long.toString(v)).orElse("?")); |
| + indexFmtLbl.setText(overviewModel.getIndexFormat().orElse("")); |
| + dirImplLbl.setText(overviewModel.getDirImpl().orElse("")); |
| + commitPointLbl.setText(overviewModel.getCommitDescription().orElse("---")); |
| + commitUserDataLbl.setText(overviewModel.getCommitUserData().orElse("---")); |
| + |
| + // term counts table |
| + Map<String, Long> termCounts = overviewModel.getSortedTermCounts(TermCountsOrder.COUNT_DESC); |
| + long numTerms = overviewModel.getNumTerms(); |
| + termCountsTable.setModel(new TermCountsTableModel(numTerms, termCounts)); |
| + termCountsTable.setRowSorter(new TableRowSorter<>(termCountsTable.getModel())); |
| + termCountsTable.getColumnModel().getColumn(TermCountsTableModel.Column.NAME.getIndex()).setMaxWidth(TermCountsTableModel.Column.NAME.getColumnWidth()); |
| + termCountsTable.getColumnModel().getColumn(TermCountsTableModel.Column.TERM_COUNT.getIndex()).setMaxWidth(TermCountsTableModel.Column.TERM_COUNT.getColumnWidth()); |
| + DefaultTableCellRenderer rightRenderer = new DefaultTableCellRenderer(); |
| + rightRenderer.setHorizontalAlignment(JLabel.RIGHT); |
| + termCountsTable.getColumnModel().getColumn(TermCountsTableModel.Column.RATIO.getIndex()).setCellRenderer(rightRenderer); |
| + |
| + // top terms table |
| + topTermsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); |
| + topTermsTable.getColumnModel().getColumn(TopTermsTableModel.Column.RANK.getIndex()).setMaxWidth(TopTermsTableModel.Column.RANK.getColumnWidth()); |
| + topTermsTable.getColumnModel().getColumn(TopTermsTableModel.Column.FREQ.getIndex()).setMaxWidth(TopTermsTableModel.Column.FREQ.getColumnWidth()); |
| + topTermsTable.getColumnModel().setColumnMargin(StyleConstants.TABLE_COLUMN_MARGIN_DEFAULT); |
| + } |
| + |
| + @Override |
| + public void closeIndex() { |
| + indexPathLbl.setText(""); |
| + numFieldsLbl.setText(""); |
| + numDocsLbl.setText(""); |
| + numTermsLbl.setText(""); |
| + delOptLbl.setText(""); |
| + indexVerLbl.setText(""); |
| + indexFmtLbl.setText(""); |
| + dirImplLbl.setText(""); |
| + commitPointLbl.setText(""); |
| + commitUserDataLbl.setText(""); |
| + |
| + selectedField.setText(""); |
| + showTopTermsBtn.setEnabled(false); |
| + |
| + termCountsTable.setRowSorter(null); |
| + termCountsTable.setModel(new TermCountsTableModel()); |
| + topTermsTable.setModel(new TopTermsTableModel()); |
| + } |
| + |
| + } |
| + |
| + static final class TermCountsTableModel extends TableModelBase<TermCountsTableModel.Column> { |
| + |
| + enum Column implements TableColumnInfo { |
| + |
| + NAME("Name", 0, String.class, 150), |
| + TERM_COUNT("Term count", 1, Long.class, 100), |
| + RATIO("%", 2, String.class, Integer.MAX_VALUE); |
| + |
| + private final String colName; |
| + private final int index; |
| + private final Class<?> type; |
| + private final int width; |
| + |
| + Column(String colName, int index, Class<?> type, int width) { |
| + this.colName = colName; |
| + this.index = index; |
| + this.type = type; |
| + this.width = width; |
| + } |
| + |
| + @Override |
| + public String getColName() { |
| + return colName; |
| + } |
| + |
| + @Override |
| + public int getIndex() { |
| + return index; |
| + } |
| + |
| + @Override |
| + public Class<?> getType() { |
| + return type; |
| + } |
| + |
| + @Override |
| + public int getColumnWidth() { |
| + return width; |
| + } |
| + } |
| + |
| + TermCountsTableModel() { |
| + super(); |
| + } |
| + |
| + TermCountsTableModel(double numTerms, Map<String, Long> termCounts) { |
| + super(termCounts.size()); |
| + int i = 0; |
| + for (Map.Entry<String, Long> e : termCounts.entrySet()) { |
| + String term = e.getKey(); |
| + Long count = e.getValue(); |
| + data[i++] = new Object[]{term, count, String.format(Locale.ENGLISH, "%.2f %%", count / numTerms * 100)}; |
| + } |
| + } |
| + |
| + @Override |
| + protected Column[] columnInfos() { |
| + return Column.values(); |
| + } |
| + } |
| + |
| + static final class TopTermsTableModel extends TableModelBase<TopTermsTableModel.Column> { |
| + |
| + enum Column implements TableColumnInfo { |
| + RANK("Rank", 0, Integer.class, 50), |
| + FREQ("Freq", 1, Integer.class, 80), |
| + TEXT("Text", 2, String.class, Integer.MAX_VALUE); |
| + |
| + private final String colName; |
| + private final int index; |
| + private final Class<?> type; |
| + private final int width; |
| + |
| + Column(String colName, int index, Class<?> type, int width) { |
| + this.colName = colName; |
| + this.index = index; |
| + this.type = type; |
| + this.width = width; |
| + } |
| + |
| + @Override |
| + public String getColName() { |
| + return colName; |
| + } |
| + |
| + @Override |
| + public int getIndex() { |
| + return index; |
| + } |
| + |
| + @Override |
| + public Class<?> getType() { |
| + return type; |
| + } |
| + |
| + @Override |
| + public int getColumnWidth() { |
| + return width; |
| + } |
| + } |
| + |
| + TopTermsTableModel() { |
| + super(); |
| + } |
| + |
| + TopTermsTableModel(List<TermStats> termStats, int numTerms) { |
| + super(Math.min(numTerms, termStats.size())); |
| + for (int i = 0; i < data.length; i++) { |
| + int rank = i + 1; |
| + int freq = termStats.get(i).getDocFreq(); |
| + String termText = termStats.get(i).getDecodedTermText(); |
| + data[i] = new Object[]{rank, freq, termText}; |
| + } |
| + } |
| + |
| + @Override |
| + protected Column[] columnInfos() { |
| + return Column.values(); |
| + } |
| + } |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/SearchPanelProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/SearchPanelProvider.java |
| new file mode 100644 |
| index 00000000000..f94517a813e |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/SearchPanelProvider.java |
| @@ -0,0 +1,834 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components; |
| + |
| +import javax.swing.BorderFactory; |
| +import javax.swing.JButton; |
| +import javax.swing.JCheckBox; |
| +import javax.swing.JFormattedTextField; |
| +import javax.swing.JLabel; |
| +import javax.swing.JMenuItem; |
| +import javax.swing.JPanel; |
| +import javax.swing.JPopupMenu; |
| +import javax.swing.JScrollPane; |
| +import javax.swing.JSeparator; |
| +import javax.swing.JSplitPane; |
| +import javax.swing.JTabbedPane; |
| +import javax.swing.JTable; |
| +import javax.swing.JTextArea; |
| +import javax.swing.ListSelectionModel; |
| +import java.awt.BorderLayout; |
| +import java.awt.Color; |
| +import java.awt.Dimension; |
| +import java.awt.FlowLayout; |
| +import java.awt.GridBagConstraints; |
| +import java.awt.GridBagLayout; |
| +import java.awt.GridLayout; |
| +import java.awt.Insets; |
| +import java.awt.event.ActionEvent; |
| +import java.awt.event.MouseAdapter; |
| +import java.awt.event.MouseEvent; |
| +import java.io.IOException; |
| +import java.util.Arrays; |
| +import java.util.Collections; |
| +import java.util.List; |
| +import java.util.Locale; |
| +import java.util.Objects; |
| +import java.util.Set; |
| +import java.util.stream.Collectors; |
| + |
| +import org.apache.lucene.analysis.Analyzer; |
| +import org.apache.lucene.analysis.standard.StandardAnalyzer; |
| +import org.apache.lucene.index.Term; |
| +import org.apache.lucene.luke.app.IndexHandler; |
| +import org.apache.lucene.luke.app.IndexObserver; |
| +import org.apache.lucene.luke.app.LukeState; |
| +import org.apache.lucene.luke.app.desktop.MessageBroker; |
| +import org.apache.lucene.luke.app.desktop.components.dialog.ConfirmDialogFactory; |
| +import org.apache.lucene.luke.app.desktop.components.dialog.search.ExplainDialogFactory; |
| +import org.apache.lucene.luke.app.desktop.components.fragments.search.AnalyzerPaneProvider; |
| +import org.apache.lucene.luke.app.desktop.components.fragments.search.FieldValuesPaneProvider; |
| +import org.apache.lucene.luke.app.desktop.components.fragments.search.FieldValuesTabOperator; |
| +import org.apache.lucene.luke.app.desktop.components.fragments.search.MLTPaneProvider; |
| +import org.apache.lucene.luke.app.desktop.components.fragments.search.MLTTabOperator; |
| +import org.apache.lucene.luke.app.desktop.components.fragments.search.QueryParserPaneProvider; |
| +import org.apache.lucene.luke.app.desktop.components.fragments.search.QueryParserTabOperator; |
| +import org.apache.lucene.luke.app.desktop.components.fragments.search.SimilarityPaneProvider; |
| +import org.apache.lucene.luke.app.desktop.components.fragments.search.SimilarityTabOperator; |
| +import org.apache.lucene.luke.app.desktop.components.fragments.search.SortPaneProvider; |
| +import org.apache.lucene.luke.app.desktop.components.fragments.search.SortTabOperator; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.FontUtils; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.StringUtils; |
| +import org.apache.lucene.luke.app.desktop.util.StyleConstants; |
| +import org.apache.lucene.luke.app.desktop.util.TabUtils; |
| +import org.apache.lucene.luke.app.desktop.util.TableUtils; |
| +import org.apache.lucene.luke.models.LukeException; |
| +import org.apache.lucene.luke.models.search.MLTConfig; |
| +import org.apache.lucene.luke.models.search.QueryParserConfig; |
| +import org.apache.lucene.luke.models.search.Search; |
| +import org.apache.lucene.luke.models.search.SearchFactory; |
| +import org.apache.lucene.luke.models.search.SearchResults; |
| +import org.apache.lucene.luke.models.search.SimilarityConfig; |
| +import org.apache.lucene.luke.models.tools.IndexTools; |
| +import org.apache.lucene.luke.models.tools.IndexToolsFactory; |
| +import org.apache.lucene.search.Explanation; |
| +import org.apache.lucene.search.Query; |
| +import org.apache.lucene.search.Sort; |
| +import org.apache.lucene.search.TermQuery; |
| +import org.apache.lucene.search.TotalHits; |
| + |
| +/** Provider of the Search panel */ |
| +public final class SearchPanelProvider implements SearchTabOperator { |
| + |
| + private static final int DEFAULT_PAGE_SIZE = 10; |
| + |
| + private final SearchFactory searchFactory; |
| + |
| + private final IndexToolsFactory toolsFactory; |
| + |
| + private final IndexHandler indexHandler; |
| + |
| + private final MessageBroker messageBroker; |
| + |
| + private final TabSwitcherProxy tabSwitcher; |
| + |
| + private final ComponentOperatorRegistry operatorRegistry; |
| + |
| + private final ConfirmDialogFactory confirmDialogFactory; |
| + |
| + private final ExplainDialogFactory explainDialogProvider; |
| + |
| + private final JTabbedPane tabbedPane = new JTabbedPane(); |
| + |
| + private final JScrollPane qparser; |
| + |
| + private final JScrollPane analyzer; |
| + |
| + private final JScrollPane similarity; |
| + |
| + private final JScrollPane sort; |
| + |
| + private final JScrollPane values; |
| + |
| + private final JScrollPane mlt; |
| + |
| + private final JCheckBox termQueryCB = new JCheckBox(); |
| + |
| + private final JTextArea queryStringTA = new JTextArea(); |
| + |
| + private final JTextArea parsedQueryTA = new JTextArea(); |
| + |
| + private final JButton parseBtn = new JButton(); |
| + |
| + private final JCheckBox rewriteCB = new JCheckBox(); |
| + |
| + private final JButton searchBtn = new JButton(); |
| + |
| + private JCheckBox exactHitsCntCB = new JCheckBox(); |
| + |
| + private final JButton mltBtn = new JButton(); |
| + |
| + private final JFormattedTextField mltDocFTF = new JFormattedTextField(); |
| + |
| + private final JLabel totalHitsLbl = new JLabel(); |
| + |
| + private final JLabel startLbl = new JLabel(); |
| + |
| + private final JLabel endLbl = new JLabel(); |
| + |
| + private final JButton prevBtn = new JButton(); |
| + |
| + private final JButton nextBtn = new JButton(); |
| + |
| + private final JButton delBtn = new JButton(); |
| + |
| + private final JTable resultsTable = new JTable(); |
| + |
| + private final ListenerFunctions listeners = new ListenerFunctions(); |
| + |
| + private Search searchModel; |
| + |
| + private IndexTools toolsModel; |
| + |
| + public SearchPanelProvider() throws IOException { |
| + this.searchFactory = new SearchFactory(); |
| + this.toolsFactory = new IndexToolsFactory(); |
| + this.indexHandler = IndexHandler.getInstance(); |
| + this.messageBroker = MessageBroker.getInstance(); |
| + this.tabSwitcher = TabSwitcherProxy.getInstance(); |
| + this.operatorRegistry = ComponentOperatorRegistry.getInstance(); |
| + this.confirmDialogFactory = ConfirmDialogFactory.getInstance(); |
| + this.explainDialogProvider = ExplainDialogFactory.getInstance(); |
| + this.qparser = new QueryParserPaneProvider().get(); |
| + this.analyzer = new AnalyzerPaneProvider().get(); |
| + this.similarity = new SimilarityPaneProvider().get(); |
| + this.sort = new SortPaneProvider().get(); |
| + this.values = new FieldValuesPaneProvider().get(); |
| + this.mlt = new MLTPaneProvider().get(); |
| + |
| + indexHandler.addObserver(new Observer()); |
| + operatorRegistry.register(SearchTabOperator.class, this); |
| + } |
| + |
| + public JPanel get() { |
| + JPanel panel = new JPanel(new GridLayout(1, 1)); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createLineBorder(Color.gray)); |
| + |
| + JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, initUpperPanel(), initLowerPanel()); |
| + splitPane.setOpaque(false); |
| + splitPane.setDividerLocation(350); |
| + panel.add(splitPane); |
| + |
| + return panel; |
| + } |
| + |
| + private JSplitPane initUpperPanel() { |
| + JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, initQuerySettingsPane(), initQueryPane()); |
| + splitPane.setOpaque(false); |
| + splitPane.setDividerLocation(570); |
| + return splitPane; |
| + } |
| + |
| + private JPanel initQuerySettingsPane() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); |
| + |
| + JLabel label = new JLabel(MessageUtils.getLocalizedMessage("search.label.settings")); |
| + panel.add(label, BorderLayout.PAGE_START); |
| + |
| + tabbedPane.addTab("Query Parser", qparser); |
| + tabbedPane.addTab("Analyzer", analyzer); |
| + tabbedPane.addTab("Similarity", similarity); |
| + tabbedPane.addTab("Sort", sort); |
| + tabbedPane.addTab("Field Values", values); |
| + tabbedPane.addTab("More Like This", mlt); |
| + |
| + TabUtils.forceTransparent(tabbedPane); |
| + |
| + panel.add(tabbedPane, BorderLayout.CENTER); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel initQueryPane() { |
| + JPanel panel = new JPanel(new GridBagLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); |
| + GridBagConstraints c = new GridBagConstraints(); |
| + c.fill = GridBagConstraints.HORIZONTAL; |
| + c.anchor = GridBagConstraints.LINE_START; |
| + |
| + JLabel labelQE = new JLabel(MessageUtils.getLocalizedMessage("search.label.expression")); |
| + c.gridx = 0; |
| + c.gridy = 0; |
| + c.gridwidth = 2; |
| + c.weightx = 0.5; |
| + c.insets = new Insets(2, 0, 2, 2); |
| + panel.add(labelQE, c); |
| + |
| + termQueryCB.setText(MessageUtils.getLocalizedMessage("search.checkbox.term")); |
| + termQueryCB.addActionListener(listeners::toggleTermQuery); |
| + termQueryCB.setOpaque(false); |
| + c.gridx = 2; |
| + c.gridy = 0; |
| + c.gridwidth = 1; |
| + c.weightx = 0.2; |
| + c.insets = new Insets(2, 0, 2, 2); |
| + panel.add(termQueryCB, c); |
| + |
| + queryStringTA.setRows(4); |
| + queryStringTA.setLineWrap(true); |
| + queryStringTA.setText("*:*"); |
| + c.gridx = 0; |
| + c.gridy = 1; |
| + c.gridwidth = 3; |
| + c.weightx = 0.0; |
| + c.insets = new Insets(2, 0, 2, 2); |
| + panel.add(new JScrollPane(queryStringTA, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER), c); |
| + |
| + JLabel labelPQ = new JLabel(MessageUtils.getLocalizedMessage("search.label.parsed")); |
| + c.gridx = 0; |
| + c.gridy = 2; |
| + c.gridwidth = 3; |
| + c.weightx = 0.0; |
| + c.insets = new Insets(8, 0, 2, 2); |
| + panel.add(labelPQ, c); |
| + |
| + parsedQueryTA.setRows(4); |
| + parsedQueryTA.setLineWrap(true); |
| + parsedQueryTA.setEditable(false); |
| + c.gridx = 0; |
| + c.gridy = 3; |
| + c.gridwidth = 3; |
| + c.weightx = 0.0; |
| + c.insets = new Insets(2, 0, 2, 2); |
| + panel.add(new JScrollPane(parsedQueryTA), c); |
| + |
| + parseBtn.setText(FontUtils.elegantIconHtml("", MessageUtils.getLocalizedMessage("search.button.parse"))); |
| + parseBtn.setFont(StyleConstants.FONT_BUTTON_LARGE); |
| + parseBtn.setMargin(new Insets(3, 0, 3, 0)); |
| + parseBtn.addActionListener(listeners::execParse); |
| + c.gridx = 0; |
| + c.gridy = 4; |
| + c.gridwidth = 1; |
| + c.weightx = 0.2; |
| + c.insets = new Insets(5, 0, 0, 2); |
| + panel.add(parseBtn, c); |
| + |
| + rewriteCB.setText(MessageUtils.getLocalizedMessage("search.checkbox.rewrite")); |
| + rewriteCB.setOpaque(false); |
| + c.gridx = 1; |
| + c.gridy = 4; |
| + c.gridwidth = 2; |
| + c.weightx = 0.2; |
| + c.insets = new Insets(5, 0, 0, 2); |
| + panel.add(rewriteCB, c); |
| + |
| + searchBtn.setText(FontUtils.elegantIconHtml("U", MessageUtils.getLocalizedMessage("search.button.search"))); |
| + searchBtn.setFont(StyleConstants.FONT_BUTTON_LARGE); |
| + searchBtn.setMargin(new Insets(3, 0, 3, 0)); |
| + searchBtn.addActionListener(listeners::execSearch); |
| + c.gridx = 0; |
| + c.gridy = 5; |
| + c.gridwidth = 1; |
| + c.weightx = 0.2; |
| + c.insets = new Insets(5, 0, 5, 0); |
| + panel.add(searchBtn, c); |
| + |
| + exactHitsCntCB.setText(MessageUtils.getLocalizedMessage("search.checkbox.exact_hits_cnt")); |
| + exactHitsCntCB.setOpaque(false); |
| + c.gridx = 1; |
| + c.gridy = 5; |
| + c.gridwidth = 2; |
| + c.weightx = 0.2; |
| + c.insets = new Insets(5, 0, 0, 2); |
| + panel.add(exactHitsCntCB, c); |
| + |
| + mltBtn.setText(FontUtils.elegantIconHtml("", MessageUtils.getLocalizedMessage("search.button.mlt"))); |
| + mltBtn.setFont(StyleConstants.FONT_BUTTON_LARGE); |
| + mltBtn.setMargin(new Insets(3, 0, 3, 0)); |
| + mltBtn.addActionListener(listeners::execMLTSearch); |
| + c.gridx = 0; |
| + c.gridy = 6; |
| + c.gridwidth = 1; |
| + c.weightx = 0.3; |
| + c.insets = new Insets(10, 0, 2, 0); |
| + panel.add(mltBtn, c); |
| + |
| + JPanel docNo = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + docNo.setOpaque(false); |
| + JLabel docNoLabel = new JLabel("with doc #"); |
| + docNo.add(docNoLabel); |
| + mltDocFTF.setColumns(8); |
| + mltDocFTF.setValue(0); |
| + docNo.add(mltDocFTF); |
| + c.gridx = 1; |
| + c.gridy = 6; |
| + c.gridwidth = 2; |
| + c.weightx = 0.3; |
| + c.insets = new Insets(8, 0, 0, 2); |
| + panel.add(docNo, c); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel initLowerPanel() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); |
| + |
| + panel.add(initSearchResultsHeaderPane(), BorderLayout.PAGE_START); |
| + panel.add(initSearchResultsTablePane(), BorderLayout.CENTER); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel initSearchResultsHeaderPane() { |
| + JPanel panel = new JPanel(new GridLayout(1, 2)); |
| + panel.setOpaque(false); |
| + |
| + JLabel label = new JLabel(FontUtils.elegantIconHtml("", MessageUtils.getLocalizedMessage("search.label.results"))); |
| + label.setHorizontalTextPosition(JLabel.LEFT); |
| + label.setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0)); |
| + panel.add(label); |
| + |
| + JPanel resultsInfo = new JPanel(new FlowLayout(FlowLayout.TRAILING)); |
| + resultsInfo.setOpaque(false); |
| + resultsInfo.setOpaque(false); |
| + |
| + JLabel totalLabel = new JLabel(MessageUtils.getLocalizedMessage("search.label.total")); |
| + resultsInfo.add(totalLabel); |
| + |
| + totalHitsLbl.setText("?"); |
| + resultsInfo.add(totalHitsLbl); |
| + |
| + prevBtn.setText(FontUtils.elegantIconHtml("D")); |
| + prevBtn.setMargin(new Insets(5, 0, 5, 0)); |
| + prevBtn.setPreferredSize(new Dimension(30, 20)); |
| + prevBtn.setEnabled(false); |
| + prevBtn.addActionListener(listeners::prevPage); |
| + resultsInfo.add(prevBtn); |
| + |
| + startLbl.setText("0"); |
| + resultsInfo.add(startLbl); |
| + |
| + resultsInfo.add(new JLabel(" ~ ")); |
| + |
| + endLbl.setText("0"); |
| + resultsInfo.add(endLbl); |
| + |
| + nextBtn.setText(FontUtils.elegantIconHtml("E")); |
| + nextBtn.setMargin(new Insets(3, 0, 3, 0)); |
| + nextBtn.setPreferredSize(new Dimension(30, 20)); |
| + nextBtn.setEnabled(false); |
| + nextBtn.addActionListener(listeners::nextPage); |
| + resultsInfo.add(nextBtn); |
| + |
| + JSeparator sep = new JSeparator(JSeparator.VERTICAL); |
| + sep.setPreferredSize(new Dimension(5, 1)); |
| + resultsInfo.add(sep); |
| + |
| + delBtn.setText(FontUtils.elegantIconHtml("", MessageUtils.getLocalizedMessage("search.button.del_all"))); |
| + delBtn.setMargin(new Insets(5, 0, 5, 0)); |
| + delBtn.setEnabled(false); |
| + delBtn.addActionListener(listeners::confirmDeletion); |
| + resultsInfo.add(delBtn); |
| + |
| + panel.add(resultsInfo, BorderLayout.CENTER); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel initSearchResultsTablePane() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + |
| + JPanel note = new JPanel(new FlowLayout(FlowLayout.LEADING, 5, 2)); |
| + note.setOpaque(false); |
| + note.add(new JLabel(MessageUtils.getLocalizedMessage("search.label.results.note"))); |
| + panel.add(note, BorderLayout.PAGE_START); |
| + |
| + TableUtils.setupTable(resultsTable, ListSelectionModel.SINGLE_SELECTION, new SearchResultsTableModel(), |
| + new MouseAdapter() { |
| + @Override |
| + public void mousePressed(MouseEvent e) { |
| + listeners.showContextMenuInResultsTable(e); |
| + } |
| + }, |
| + SearchResultsTableModel.Column.DOCID.getColumnWidth(), |
| + SearchResultsTableModel.Column.SCORE.getColumnWidth()); |
| + JScrollPane scrollPane = new JScrollPane(resultsTable); |
| + panel.add(scrollPane, BorderLayout.CENTER); |
| + |
| + return panel; |
| + } |
| + |
| + // control methods |
| + |
| + private void toggleTermQuery() { |
| + if (termQueryCB.isSelected()) { |
| + enableTermQuery(); |
| + } else { |
| + disableTermQuery(); |
| + } |
| + } |
| + |
| + private void enableTermQuery() { |
| + tabbedPane.setEnabledAt(Tab.QPARSER.index(), false); |
| + tabbedPane.setEnabledAt(Tab.ANALYZER.index(), false); |
| + tabbedPane.setEnabledAt(Tab.SIMILARITY.index(), false); |
| + if (tabbedPane.getSelectedIndex() == Tab.QPARSER.index() || |
| + tabbedPane.getSelectedIndex() == Tab.ANALYZER.index() || |
| + tabbedPane.getSelectedIndex() == Tab.SIMILARITY.index() || |
| + tabbedPane.getSelectedIndex() == Tab.MLT.index()) { |
| + tabbedPane.setSelectedIndex(Tab.SORT.index()); |
| + } |
| + parseBtn.setEnabled(false); |
| + rewriteCB.setEnabled(false); |
| + } |
| + |
| + private void disableTermQuery() { |
| + tabbedPane.setEnabledAt(Tab.QPARSER.index(), true); |
| + tabbedPane.setEnabledAt(Tab.ANALYZER.index(), true); |
| + tabbedPane.setEnabledAt(Tab.SIMILARITY.index(), true); |
| + parseBtn.setEnabled(true); |
| + rewriteCB.setEnabled(true); |
| + } |
| + |
| + private void execParse() { |
| + Query query = parse(rewriteCB.isSelected()); |
| + parsedQueryTA.setText(query.toString()); |
| + messageBroker.clearStatusMessage(); |
| + } |
| + |
| + private void doSearch() { |
| + Query query; |
| + if (termQueryCB.isSelected()) { |
| + // term query |
| + if (StringUtils.isNullOrEmpty(queryStringTA.getText())) { |
| + throw new LukeException("Query is not set."); |
| + } |
| + String[] tmp = queryStringTA.getText().split(":"); |
| + if (tmp.length < 2) { |
| + throw new LukeException(String.format(Locale.ENGLISH, "Invalid query [ %s ]", queryStringTA.getText())); |
| + } |
| + query = new TermQuery(new Term(tmp[0].trim(), tmp[1].trim())); |
| + } else { |
| + query = parse(false); |
| + } |
| + SimilarityConfig simConfig = operatorRegistry.get(SimilarityTabOperator.class) |
| + .map(SimilarityTabOperator::getConfig) |
| + .orElse(new SimilarityConfig.Builder().build()); |
| + Sort sort = operatorRegistry.get(SortTabOperator.class) |
| + .map(SortTabOperator::getSort) |
| + .orElse(null); |
| + Set<String> fieldsToLoad = operatorRegistry.get(FieldValuesTabOperator.class) |
| + .map(FieldValuesTabOperator::getFieldsToLoad) |
| + .orElse(Collections.emptySet()); |
| + SearchResults results = searchModel.search(query, simConfig, sort, fieldsToLoad, DEFAULT_PAGE_SIZE, exactHitsCntCB.isSelected()); |
| + |
| + TableUtils.setupTable(resultsTable, ListSelectionModel.SINGLE_SELECTION, new SearchResultsTableModel(), null, |
| + SearchResultsTableModel.Column.DOCID.getColumnWidth(), |
| + SearchResultsTableModel.Column.SCORE.getColumnWidth()); |
| + populateResults(results); |
| + |
| + messageBroker.clearStatusMessage(); |
| + } |
| + |
| + private void nextPage() { |
| + searchModel.nextPage().ifPresent(this::populateResults); |
| + messageBroker.clearStatusMessage(); |
| + } |
| + |
| + private void prevPage() { |
| + searchModel.prevPage().ifPresent(this::populateResults); |
| + messageBroker.clearStatusMessage(); |
| + } |
| + |
| + private void doMLTSearch() { |
| + if (Objects.isNull(mltDocFTF.getValue())) { |
| + throw new LukeException("Doc num is not set."); |
| + } |
| + int docNum = (int) mltDocFTF.getValue(); |
| + MLTConfig mltConfig = operatorRegistry.get(MLTTabOperator.class) |
| + .map(MLTTabOperator::getConfig) |
| + .orElse(new MLTConfig.Builder().build()); |
| + Analyzer analyzer = operatorRegistry.get(AnalysisTabOperator.class) |
| + .map(AnalysisTabOperator::getCurrentAnalyzer) |
| + .orElse(new StandardAnalyzer()); |
| + Query query = searchModel.mltQuery(docNum, mltConfig, analyzer); |
| + Set<String> fieldsToLoad = operatorRegistry.get(FieldValuesTabOperator.class) |
| + .map(FieldValuesTabOperator::getFieldsToLoad) |
| + .orElse(Collections.emptySet()); |
| + SearchResults results = searchModel.search(query, new SimilarityConfig.Builder().build(), fieldsToLoad, DEFAULT_PAGE_SIZE, false); |
| + |
| + TableUtils.setupTable(resultsTable, ListSelectionModel.SINGLE_SELECTION, new SearchResultsTableModel(), null, |
| + SearchResultsTableModel.Column.DOCID.getColumnWidth(), |
| + SearchResultsTableModel.Column.SCORE.getColumnWidth()); |
| + populateResults(results); |
| + |
| + messageBroker.clearStatusMessage(); |
| + } |
| + |
| + private Query parse(boolean rewrite) { |
| + String expr = StringUtils.isNullOrEmpty(queryStringTA.getText()) ? "*:*" : queryStringTA.getText(); |
| + String df = operatorRegistry.get(QueryParserTabOperator.class) |
| + .map(QueryParserTabOperator::getDefaultField) |
| + .orElse(""); |
| + QueryParserConfig config = operatorRegistry.get(QueryParserTabOperator.class) |
| + .map(QueryParserTabOperator::getConfig) |
| + .orElse(new QueryParserConfig.Builder().build()); |
| + Analyzer analyzer = operatorRegistry.get(AnalysisTabOperator.class) |
| + .map(AnalysisTabOperator::getCurrentAnalyzer) |
| + .orElse(new StandardAnalyzer()); |
| + return searchModel.parseQuery(expr, df, analyzer, config, rewrite); |
| + } |
| + |
| + private void populateResults(SearchResults res) { |
| + totalHitsLbl.setText(String.valueOf(res.getTotalHits())); |
| + if (res.getTotalHits().value > 0) { |
| + startLbl.setText(String.valueOf(res.getOffset() + 1)); |
| + endLbl.setText(String.valueOf(res.getOffset() + res.size())); |
| + |
| + prevBtn.setEnabled(res.getOffset() > 0); |
| + nextBtn.setEnabled(res.getTotalHits().relation == TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO || res.getTotalHits().value > res.getOffset() + res.size()); |
| + |
| + if (!indexHandler.getState().readOnly() && indexHandler.getState().hasDirectoryReader()) { |
| + delBtn.setEnabled(true); |
| + } |
| + |
| + resultsTable.setModel(new SearchResultsTableModel(res)); |
| + resultsTable.getColumnModel().getColumn(SearchResultsTableModel.Column.DOCID.getIndex()).setPreferredWidth(SearchResultsTableModel.Column.DOCID.getColumnWidth()); |
| + resultsTable.getColumnModel().getColumn(SearchResultsTableModel.Column.SCORE.getIndex()).setPreferredWidth(SearchResultsTableModel.Column.SCORE.getColumnWidth()); |
| + resultsTable.getColumnModel().getColumn(SearchResultsTableModel.Column.VALUE.getIndex()).setPreferredWidth(SearchResultsTableModel.Column.VALUE.getColumnWidth()); |
| + } else { |
| + startLbl.setText("0"); |
| + endLbl.setText("0"); |
| + prevBtn.setEnabled(false); |
| + nextBtn.setEnabled(false); |
| + delBtn.setEnabled(false); |
| + } |
| + } |
| + |
| + private void confirmDeletion() { |
| + new DialogOpener<>(confirmDialogFactory).open("Confirm Deletion", 400, 200, (factory) -> { |
| + factory.setMessage(MessageUtils.getLocalizedMessage("search.message.delete_confirm")); |
| + factory.setCallback(this::deleteDocs); |
| + }); |
| + } |
| + |
| + private void deleteDocs() { |
| + Query query = searchModel.getCurrentQuery(); |
| + if (query != null) { |
| + toolsModel.deleteDocuments(query); |
| + indexHandler.reOpen(); |
| + messageBroker.showStatusMessage(MessageUtils.getLocalizedMessage("search.message.delete_success", query.toString())); |
| + } |
| + delBtn.setEnabled(false); |
| + } |
| + |
| + private JPopupMenu setupResultsContextMenuPopup() { |
| + JPopupMenu popup = new JPopupMenu(); |
| + |
| + // show explanation |
| + JMenuItem item1 = new JMenuItem(MessageUtils.getLocalizedMessage("search.results.menu.explain")); |
| + item1.addActionListener(e -> { |
| + int docid = (int) resultsTable.getModel().getValueAt(resultsTable.getSelectedRow(), SearchResultsTableModel.Column.DOCID.getIndex()); |
| + Explanation explanation = searchModel.explain(parse(false), docid); |
| + new DialogOpener<>(explainDialogProvider).open("Explanation", 600, 400, |
| + (factory) -> { |
| + factory.setDocid(docid); |
| + factory.setExplanation(explanation); |
| + }); |
| + }); |
| + popup.add(item1); |
| + |
| + // show all fields |
| + JMenuItem item2 = new JMenuItem(MessageUtils.getLocalizedMessage("search.results.menu.showdoc")); |
| + item2.addActionListener(e -> { |
| + int docid = (int) resultsTable.getModel().getValueAt(resultsTable.getSelectedRow(), SearchResultsTableModel.Column.DOCID.getIndex()); |
| + operatorRegistry.get(DocumentsTabOperator.class).ifPresent(operator -> operator.displayDoc(docid)); |
| + tabSwitcher.switchTab(TabbedPaneProvider.Tab.DOCUMENTS); |
| + }); |
| + popup.add(item2); |
| + |
| + return popup; |
| + } |
| + |
| + @Override |
| + public void searchByTerm(String field, String term) { |
| + termQueryCB.setSelected(true); |
| + enableTermQuery(); |
| + queryStringTA.setText(field + ":" + term); |
| + doSearch(); |
| + } |
| + |
| + @Override |
| + public void mltSearch(int docNum) { |
| + mltDocFTF.setValue(docNum); |
| + doMLTSearch(); |
| + tabbedPane.setSelectedIndex(Tab.MLT.index()); |
| + } |
| + |
| + @Override |
| + public void enableExactHitsCB(boolean value) { |
| + exactHitsCntCB.setEnabled(value); |
| + } |
| + |
| + @Override |
| + public void setExactHits(boolean value) { |
| + exactHitsCntCB.setSelected(value); |
| + } |
| + |
| + private class ListenerFunctions { |
| + |
| + void toggleTermQuery(ActionEvent e) { |
| + SearchPanelProvider.this.toggleTermQuery(); |
| + } |
| + |
| + void execParse(ActionEvent e) { |
| + SearchPanelProvider.this.execParse(); |
| + } |
| + |
| + void execSearch(ActionEvent e) { |
| + SearchPanelProvider.this.doSearch(); |
| + } |
| + |
| + void nextPage(ActionEvent e) { |
| + SearchPanelProvider.this.nextPage(); |
| + } |
| + |
| + void prevPage(ActionEvent e) { |
| + SearchPanelProvider.this.prevPage(); |
| + } |
| + |
| + void execMLTSearch(ActionEvent e) { |
| + SearchPanelProvider.this.doMLTSearch(); |
| + } |
| + |
| + void confirmDeletion(ActionEvent e) { |
| + SearchPanelProvider.this.confirmDeletion(); |
| + } |
| + |
| + void showContextMenuInResultsTable(MouseEvent e) { |
| + if (e.getClickCount() == 2 && !e.isConsumed()) { |
| + SearchPanelProvider.this.setupResultsContextMenuPopup().show(e.getComponent(), e.getX(), e.getY()); |
| + setupResultsContextMenuPopup().show(e.getComponent(), e.getX(), e.getY()); |
| + } |
| + } |
| + |
| + } |
| + |
| + private class Observer implements IndexObserver { |
| + |
| + @Override |
| + public void openIndex(LukeState state) { |
| + searchModel = searchFactory.newInstance(state.getIndexReader()); |
| + toolsModel = toolsFactory.newInstance(state.getIndexReader(), state.useCompound(), state.keepAllCommits()); |
| + operatorRegistry.get(QueryParserTabOperator.class).ifPresent(operator -> { |
| + operator.setSearchableFields(searchModel.getSearchableFieldNames()); |
| + operator.setRangeSearchableFields(searchModel.getRangeSearchableFieldNames()); |
| + }); |
| + operatorRegistry.get(SortTabOperator.class).ifPresent(operator -> { |
| + operator.setSearchModel(searchModel); |
| + operator.setSortableFields(searchModel.getSortableFieldNames()); |
| + }); |
| + operatorRegistry.get(FieldValuesTabOperator.class).ifPresent(operator -> { |
| + operator.setFields(searchModel.getFieldNames()); |
| + }); |
| + operatorRegistry.get(MLTTabOperator.class).ifPresent(operator -> { |
| + operator.setFields(searchModel.getFieldNames()); |
| + }); |
| + |
| + queryStringTA.setText("*:*"); |
| + parsedQueryTA.setText(""); |
| + parseBtn.setEnabled(true); |
| + searchBtn.setEnabled(true); |
| + mltBtn.setEnabled(true); |
| + } |
| + |
| + @Override |
| + public void closeIndex() { |
| + searchModel = null; |
| + toolsModel = null; |
| + |
| + queryStringTA.setText(""); |
| + parsedQueryTA.setText(""); |
| + parseBtn.setEnabled(false); |
| + searchBtn.setEnabled(false); |
| + mltBtn.setEnabled(false); |
| + totalHitsLbl.setText("0"); |
| + startLbl.setText("0"); |
| + endLbl.setText("0"); |
| + nextBtn.setEnabled(false); |
| + prevBtn.setEnabled(false); |
| + delBtn.setEnabled(false); |
| + TableUtils.setupTable(resultsTable, ListSelectionModel.SINGLE_SELECTION, new SearchResultsTableModel(), null, |
| + SearchResultsTableModel.Column.DOCID.getColumnWidth(), |
| + SearchResultsTableModel.Column.SCORE.getColumnWidth()); |
| + } |
| + |
| + } |
| + |
| + /** tabs in the Search panel */ |
| + public enum Tab { |
| + QPARSER(0), ANALYZER(1), SIMILARITY(2), SORT(3), VALUES(4), MLT(5); |
| + |
| + private int tabIdx; |
| + |
| + Tab(int tabIdx) { |
| + this.tabIdx = tabIdx; |
| + } |
| + |
| + int index() { |
| + return tabIdx; |
| + } |
| + } |
| + |
| + static final class SearchResultsTableModel extends TableModelBase<SearchResultsTableModel.Column> { |
| + |
| + enum Column implements TableColumnInfo { |
| + DOCID("Doc ID", 0, Integer.class, 50), |
| + SCORE("Score", 1, Float.class, 100), |
| + VALUE("Field Values", 2, String.class, 800); |
| + |
| + private final String colName; |
| + private final int index; |
| + private final Class<?> type; |
| + private final int width; |
| + |
| + Column(String colName, int index, Class<?> type, int width) { |
| + this.colName = colName; |
| + this.index = index; |
| + this.type = type; |
| + this.width = width; |
| + } |
| + |
| + @Override |
| + public String getColName() { |
| + return colName; |
| + } |
| + |
| + @Override |
| + public int getIndex() { |
| + return index; |
| + } |
| + |
| + @Override |
| + public Class<?> getType() { |
| + return type; |
| + } |
| + |
| + @Override |
| + public int getColumnWidth() { |
| + return width; |
| + } |
| + } |
| + |
| + SearchResultsTableModel() { |
| + super(); |
| + } |
| + |
| + SearchResultsTableModel(SearchResults results) { |
| + super(results.size()); |
| + for (int i = 0; i < results.size(); i++) { |
| + SearchResults.Doc doc = results.getHits().get(i); |
| + data[i][Column.DOCID.getIndex()] = doc.getDocId(); |
| + if (!Float.isNaN(doc.getScore())) { |
| + data[i][Column.SCORE.getIndex()] = doc.getScore(); |
| + } else { |
| + data[i][Column.SCORE.getIndex()] = 1.0f; |
| + } |
| + List<String> concatValues = doc.getFieldValues().entrySet().stream().map(e -> { |
| + String v = String.join(",", Arrays.asList(e.getValue())); |
| + return e.getKey() + "=" + v + ";"; |
| + }).collect(Collectors.toList()); |
| + data[i][Column.VALUE.getIndex()] = String.join(" ", concatValues); |
| + } |
| + } |
| + |
| + @Override |
| + protected Column[] columnInfos() { |
| + return Column.values(); |
| + } |
| + } |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/SearchTabOperator.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/SearchTabOperator.java |
| new file mode 100644 |
| index 00000000000..05e70026914 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/SearchTabOperator.java |
| @@ -0,0 +1,29 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components; |
| + |
| +/** Operator for the Search tab */ |
| +public interface SearchTabOperator extends ComponentOperatorRegistry.ComponentOperator { |
| + void searchByTerm(String field, String term); |
| + |
| + void mltSearch(int docNum); |
| + |
| + void enableExactHitsCB(boolean value); |
| + |
| + void setExactHits(boolean value); |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TabSwitcherProxy.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TabSwitcherProxy.java |
| new file mode 100644 |
| index 00000000000..42f2194c5ee |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TabSwitcherProxy.java |
| @@ -0,0 +1,49 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components; |
| + |
| +/** An utility class for switching tabs. */ |
| +public class TabSwitcherProxy { |
| + |
| + private static final TabSwitcherProxy instance = new TabSwitcherProxy(); |
| + |
| + private TabSwitcher switcher; |
| + |
| + public static TabSwitcherProxy getInstance() { |
| + return instance; |
| + } |
| + |
| + public void set(TabSwitcher switcher) { |
| + if (this.switcher == null) { |
| + this.switcher = switcher; |
| + } |
| + } |
| + |
| + public void switchTab(TabbedPaneProvider.Tab tab) { |
| + if (switcher == null) { |
| + throw new IllegalStateException(); |
| + } |
| + switcher.switchTab(tab); |
| + } |
| + |
| + /** tab switcher */ |
| + public interface TabSwitcher { |
| + void switchTab(TabbedPaneProvider.Tab tab); |
| + } |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TabbedPaneProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TabbedPaneProvider.java |
| new file mode 100644 |
| index 00000000000..c5fd73a0f68 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TabbedPaneProvider.java |
| @@ -0,0 +1,137 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components; |
| + |
| +import javax.swing.JPanel; |
| +import javax.swing.JTabbedPane; |
| +import javax.swing.JTextArea; |
| +import java.io.IOException; |
| + |
| +import org.apache.lucene.luke.app.DirectoryHandler; |
| +import org.apache.lucene.luke.app.DirectoryObserver; |
| +import org.apache.lucene.luke.app.IndexHandler; |
| +import org.apache.lucene.luke.app.IndexObserver; |
| +import org.apache.lucene.luke.app.LukeState; |
| +import org.apache.lucene.luke.app.desktop.MessageBroker; |
| +import org.apache.lucene.luke.app.desktop.util.FontUtils; |
| +import org.apache.lucene.luke.app.desktop.util.TabUtils; |
| + |
| +/** Provider of the Tabbed pane */ |
| +public final class TabbedPaneProvider implements TabSwitcherProxy.TabSwitcher { |
| + |
| + private final MessageBroker messageBroker; |
| + |
| + private final JTabbedPane tabbedPane = new JTabbedPane(); |
| + |
| + private final JPanel overviewPanel; |
| + |
| + private final JPanel documentsPanel; |
| + |
| + private final JPanel searchPanel; |
| + |
| + private final JPanel analysisPanel; |
| + |
| + private final JPanel commitsPanel; |
| + |
| + private final JPanel logsPanel; |
| + |
| + public TabbedPaneProvider(JTextArea logTextArea) throws IOException { |
| + this.overviewPanel = new OverviewPanelProvider().get(); |
| + this.documentsPanel = new DocumentsPanelProvider().get(); |
| + this.searchPanel = new SearchPanelProvider().get(); |
| + this.analysisPanel = new AnalysisPanelProvider().get(); |
| + this.commitsPanel = new CommitsPanelProvider().get(); |
| + this.logsPanel = new LogsPanelProvider(logTextArea).get(); |
| + |
| + this.messageBroker = MessageBroker.getInstance(); |
| + |
| + TabSwitcherProxy.getInstance().set(this); |
| + |
| + Observer observer = new Observer(); |
| + IndexHandler.getInstance().addObserver(observer); |
| + DirectoryHandler.getInstance().addObserver(observer); |
| + } |
| + |
| + public JTabbedPane get() { |
| + tabbedPane.addTab(FontUtils.elegantIconHtml("", "Overview"), overviewPanel); |
| + tabbedPane.addTab(FontUtils.elegantIconHtml("i", "Documents"), documentsPanel); |
| + tabbedPane.addTab(FontUtils.elegantIconHtml("", "Search"), searchPanel); |
| + tabbedPane.addTab(FontUtils.elegantIconHtml("", "Analysis"), analysisPanel); |
| + tabbedPane.addTab(FontUtils.elegantIconHtml("", "Commits"), commitsPanel); |
| + tabbedPane.addTab(FontUtils.elegantIconHtml("", "Logs"), logsPanel); |
| + |
| + TabUtils.forceTransparent(tabbedPane); |
| + |
| + return tabbedPane; |
| + } |
| + |
| + public void switchTab(Tab tab) { |
| + tabbedPane.setSelectedIndex(tab.index()); |
| + tabbedPane.setVisible(false); |
| + tabbedPane.setVisible(true); |
| + messageBroker.clearStatusMessage(); |
| + } |
| + |
| + private class Observer implements IndexObserver, DirectoryObserver { |
| + |
| + @Override |
| + public void openDirectory(LukeState state) { |
| + tabbedPane.setEnabledAt(Tab.COMMITS.index(), true); |
| + } |
| + |
| + @Override |
| + public void closeDirectory() { |
| + tabbedPane.setEnabledAt(Tab.OVERVIEW.index(), false); |
| + tabbedPane.setEnabledAt(Tab.DOCUMENTS.index(), false); |
| + tabbedPane.setEnabledAt(Tab.SEARCH.index(), false); |
| + tabbedPane.setEnabledAt(Tab.COMMITS.index(), false); |
| + } |
| + |
| + @Override |
| + public void openIndex(LukeState state) { |
| + tabbedPane.setEnabledAt(Tab.OVERVIEW.index(), true); |
| + tabbedPane.setEnabledAt(Tab.DOCUMENTS.index(), true); |
| + tabbedPane.setEnabledAt(Tab.SEARCH.index(), true); |
| + tabbedPane.setEnabledAt(Tab.COMMITS.index(), true); |
| + } |
| + |
| + @Override |
| + public void closeIndex() { |
| + tabbedPane.setEnabledAt(Tab.OVERVIEW.index(), false); |
| + tabbedPane.setEnabledAt(Tab.DOCUMENTS.index(), false); |
| + tabbedPane.setEnabledAt(Tab.SEARCH.index(), false); |
| + tabbedPane.setEnabledAt(Tab.COMMITS.index(), false); |
| + } |
| + } |
| + |
| + /** tabs in the main frame */ |
| + public enum Tab { |
| + OVERVIEW(0), DOCUMENTS(1), SEARCH(2), ANALYZER(3), COMMITS(4); |
| + |
| + private int tabIdx; |
| + |
| + Tab(int tabIdx) { |
| + this.tabIdx = tabIdx; |
| + } |
| + |
| + int index() { |
| + return tabIdx; |
| + } |
| + } |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TableColumnInfo.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TableColumnInfo.java |
| new file mode 100644 |
| index 00000000000..63cdbb10700 |
| --- /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 00000000000..f8ef21a41ef |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TableModelBase.java |
| @@ -0,0 +1,75 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components; |
| + |
| +import javax.swing.table.AbstractTableModel; |
| +import java.util.Map; |
| + |
| +import org.apache.lucene.luke.app.desktop.util.TableUtils; |
| + |
| +/** Base table model that stores table's meta data and content. This also provides some default implementation of the {@link javax.swing.table.TableModel} interface. */ |
| +public abstract class TableModelBase<T extends TableColumnInfo> extends AbstractTableModel { |
| + |
| + private final Map<Integer, T> columnMap = TableUtils.columnMap(columnInfos()); |
| + |
| + private final String[] colNames = TableUtils.columnNames(columnInfos()); |
| + |
| + protected final Object[][] data; |
| + |
| + protected TableModelBase() { |
| + this.data = new Object[0][colNames.length]; |
| + } |
| + |
| + protected TableModelBase(int rows) { |
| + this.data = new Object[rows][colNames.length]; |
| + } |
| + |
| + protected abstract T[] columnInfos(); |
| + |
| + @Override |
| + public int getRowCount() { |
| + return data.length; |
| + } |
| + |
| + @Override |
| + public int getColumnCount() { |
| + return colNames.length; |
| + } |
| + |
| + @Override |
| + public String getColumnName(int colIndex) { |
| + if (columnMap.containsKey(colIndex)) { |
| + return columnMap.get(colIndex).getColName(); |
| + } |
| + return ""; |
| + } |
| + |
| + @Override |
| + public Class<?> getColumnClass(int colIndex) { |
| + if (columnMap.containsKey(colIndex)) { |
| + return columnMap.get(colIndex).getType(); |
| + } |
| + return Object.class; |
| + } |
| + |
| + |
| + @Override |
| + public Object getValueAt(int rowIndex, int columnIndex) { |
| + return data[rowIndex][columnIndex]; |
| + } |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/ConfirmDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/ConfirmDialogFactory.java |
| new file mode 100644 |
| index 00000000000..d5465984edf |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/ConfirmDialogFactory.java |
| @@ -0,0 +1,119 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components.dialog; |
| + |
| +import javax.swing.BorderFactory; |
| +import javax.swing.JButton; |
| +import javax.swing.JDialog; |
| +import javax.swing.JLabel; |
| +import javax.swing.JPanel; |
| +import java.awt.BorderLayout; |
| +import java.awt.Color; |
| +import java.awt.Dialog; |
| +import java.awt.Dimension; |
| +import java.awt.FlowLayout; |
| +import java.awt.Font; |
| +import java.awt.GridLayout; |
| +import java.awt.Window; |
| +import java.io.IOException; |
| + |
| +import org.apache.lucene.luke.app.desktop.Preferences; |
| +import org.apache.lucene.luke.app.desktop.PreferencesFactory; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.FontUtils; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.lang.Callable; |
| + |
| +/** Factory of confirm dialog */ |
| +public final class ConfirmDialogFactory implements DialogOpener.DialogFactory { |
| + |
| + private static ConfirmDialogFactory instance; |
| + |
| + private final Preferences prefs; |
| + |
| + private JDialog dialog; |
| + |
| + private String message; |
| + |
| + private Callable callback; |
| + |
| + public synchronized static ConfirmDialogFactory getInstance() throws IOException { |
| + if (instance == null) { |
| + instance = new ConfirmDialogFactory(); |
| + } |
| + return instance; |
| + } |
| + |
| + private ConfirmDialogFactory() throws IOException { |
| + this.prefs = PreferencesFactory.getInstance(); |
| + } |
| + |
| + public void setMessage(String message) { |
| + this.message = message; |
| + } |
| + |
| + public void setCallback(Callable callback) { |
| + this.callback = callback; |
| + } |
| + |
| + @Override |
| + public JDialog create(Window owner, String title, int width, int height) { |
| + dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL); |
| + dialog.add(content()); |
| + dialog.setSize(new Dimension(width, height)); |
| + dialog.setLocationRelativeTo(owner); |
| + dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor()); |
| + return dialog; |
| + } |
| + |
| + private JPanel content() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); |
| + |
| + JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + header.setOpaque(false); |
| + JLabel alertIconLbl = new JLabel(FontUtils.elegantIconHtml("q")); |
| + alertIconLbl.setHorizontalAlignment(JLabel.CENTER); |
| + alertIconLbl.setFont(new Font(alertIconLbl.getFont().getFontName(), Font.PLAIN, 25)); |
| + header.add(alertIconLbl); |
| + panel.add(header, BorderLayout.PAGE_START); |
| + |
| + JPanel center = new JPanel(new GridLayout(1, 1)); |
| + center.setOpaque(false); |
| + center.setBorder(BorderFactory.createLineBorder(Color.gray, 3)); |
| + center.add(new JLabel(message, JLabel.CENTER)); |
| + panel.add(center, BorderLayout.CENTER); |
| + |
| + JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING)); |
| + footer.setOpaque(false); |
| + JButton okBtn = new JButton(MessageUtils.getLocalizedMessage("button.ok")); |
| + okBtn.addActionListener(e -> { |
| + callback.call(); |
| + dialog.dispose(); |
| + }); |
| + footer.add(okBtn); |
| + JButton closeBtn = new JButton(MessageUtils.getLocalizedMessage("button.close")); |
| + closeBtn.addActionListener(e -> dialog.dispose()); |
| + footer.add(closeBtn); |
| + panel.add(footer, BorderLayout.PAGE_END); |
| + |
| + return panel; |
| + } |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/HelpDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/HelpDialogFactory.java |
| new file mode 100644 |
| index 00000000000..b9bcf9d2f78 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/HelpDialogFactory.java |
| @@ -0,0 +1,106 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components.dialog; |
| + |
| +import javax.swing.BorderFactory; |
| +import javax.swing.JButton; |
| +import javax.swing.JComponent; |
| +import javax.swing.JDialog; |
| +import javax.swing.JLabel; |
| +import javax.swing.JPanel; |
| +import java.awt.BorderLayout; |
| +import java.awt.Dialog; |
| +import java.awt.Dimension; |
| +import java.awt.FlowLayout; |
| +import java.awt.GridLayout; |
| +import java.awt.Window; |
| +import java.io.IOException; |
| + |
| +import org.apache.lucene.luke.app.desktop.Preferences; |
| +import org.apache.lucene.luke.app.desktop.PreferencesFactory; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| + |
| +/** Factory of help dialog */ |
| +public final class HelpDialogFactory implements DialogOpener.DialogFactory { |
| + |
| + private static HelpDialogFactory instance; |
| + |
| + private final Preferences prefs; |
| + |
| + private JDialog dialog; |
| + |
| + private String desc; |
| + |
| + private JComponent helpContent; |
| + |
| + public synchronized static HelpDialogFactory getInstance() throws IOException { |
| + if (instance == null) { |
| + instance = new HelpDialogFactory(); |
| + } |
| + return instance; |
| + } |
| + |
| + private HelpDialogFactory() throws IOException { |
| + this.prefs = PreferencesFactory.getInstance(); |
| + } |
| + |
| + public void setDesc(String desc) { |
| + this.desc = desc; |
| + } |
| + |
| + public void setContent(JComponent helpContent) { |
| + this.helpContent = helpContent; |
| + } |
| + |
| + @Override |
| + public JDialog create(Window owner, String title, int width, int height) { |
| + dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL); |
| + dialog.add(content()); |
| + dialog.setSize(new Dimension(width, height)); |
| + dialog.setLocationRelativeTo(owner); |
| + dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor()); |
| + return dialog; |
| + } |
| + |
| + private JPanel content() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); |
| + |
| + JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + header.setOpaque(false); |
| + header.add(new JLabel(desc)); |
| + panel.add(header, BorderLayout.PAGE_START); |
| + |
| + JPanel center = new JPanel(new GridLayout(1, 1)); |
| + center.setOpaque(false); |
| + center.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); |
| + center.add(helpContent); |
| + panel.add(center, BorderLayout.CENTER); |
| + |
| + JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING)); |
| + footer.setOpaque(false); |
| + JButton closeBtn = new JButton(MessageUtils.getLocalizedMessage("button.close")); |
| + closeBtn.addActionListener(e -> dialog.dispose()); |
| + footer.add(closeBtn); |
| + panel.add(footer, BorderLayout.PAGE_END); |
| + |
| + return panel; |
| + } |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/AnalysisChainDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/AnalysisChainDialogFactory.java |
| new file mode 100644 |
| index 00000000000..31fce6d05b3 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/AnalysisChainDialogFactory.java |
| @@ -0,0 +1,158 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components.dialog.analysis; |
| + |
| +import javax.swing.BorderFactory; |
| +import javax.swing.JButton; |
| +import javax.swing.JDialog; |
| +import javax.swing.JLabel; |
| +import javax.swing.JList; |
| +import javax.swing.JPanel; |
| +import javax.swing.JScrollPane; |
| +import javax.swing.JTextField; |
| +import java.awt.BorderLayout; |
| +import java.awt.Color; |
| +import java.awt.Dialog; |
| +import java.awt.Dimension; |
| +import java.awt.FlowLayout; |
| +import java.awt.GridBagConstraints; |
| +import java.awt.GridBagLayout; |
| +import java.awt.Insets; |
| +import java.awt.Window; |
| +import java.io.IOException; |
| + |
| +import org.apache.lucene.analysis.custom.CustomAnalyzer; |
| +import org.apache.lucene.luke.app.desktop.Preferences; |
| +import org.apache.lucene.luke.app.desktop.PreferencesFactory; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| + |
| +/** Factory of analysis chain dialog */ |
| +public class AnalysisChainDialogFactory implements DialogOpener.DialogFactory { |
| + |
| + private static AnalysisChainDialogFactory instance; |
| + |
| + private final Preferences prefs; |
| + |
| + private JDialog dialog; |
| + |
| + private CustomAnalyzer analyzer; |
| + |
| + public synchronized static AnalysisChainDialogFactory getInstance() throws IOException { |
| + if (instance == null) { |
| + instance = new AnalysisChainDialogFactory(); |
| + } |
| + return instance; |
| + } |
| + |
| + private AnalysisChainDialogFactory() throws IOException { |
| + this.prefs = PreferencesFactory.getInstance(); |
| + } |
| + |
| + public void setAnalyzer(CustomAnalyzer analyzer) { |
| + this.analyzer = analyzer; |
| + } |
| + |
| + @Override |
| + public JDialog create(Window owner, String title, int width, int height) { |
| + dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL); |
| + dialog.add(content()); |
| + dialog.setSize(new Dimension(width, height)); |
| + dialog.setLocationRelativeTo(owner); |
| + dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor()); |
| + return dialog; |
| + } |
| + |
| + private JPanel content() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); |
| + |
| + panel.add(analysisChain(), BorderLayout.PAGE_START); |
| + |
| + JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING, 10, 5)); |
| + footer.setOpaque(false); |
| + JButton closeBtn = new JButton(MessageUtils.getLocalizedMessage("button.close")); |
| + closeBtn.addActionListener(e -> dialog.dispose()); |
| + footer.add(closeBtn); |
| + panel.add(footer, BorderLayout.PAGE_END); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel analysisChain() { |
| + JPanel panel = new JPanel(new GridBagLayout()); |
| + panel.setOpaque(false); |
| + |
| + GridBagConstraints c = new GridBagConstraints(); |
| + c.fill = GridBagConstraints.HORIZONTAL; |
| + c.insets = new Insets(5, 5, 5, 5); |
| + |
| + c.gridx = 0; |
| + c.gridy = 0; |
| + c.weightx = 0.1; |
| + c.weighty = 0.5; |
| + panel.add(new JLabel(MessageUtils.getLocalizedMessage("analysis.dialog.chain.label.charfilters")), c); |
| + |
| + String[] charFilters = analyzer.getCharFilterFactories().stream().map(f -> f.getClass().getName()).toArray(String[]::new); |
| + JList<String> charFilterList = new JList<>(charFilters); |
| + charFilterList.setVisibleRowCount(charFilters.length == 0 ? 1 : Math.min(charFilters.length, 5)); |
| + c.gridx = 1; |
| + c.gridy = 0; |
| + c.weightx = 0.5; |
| + c.weighty = 0.5; |
| + panel.add(new JScrollPane(charFilterList), c); |
| + |
| + c.gridx = 0; |
| + c.gridy = 1; |
| + c.weightx = 0.1; |
| + c.weighty = 0.1; |
| + panel.add(new JLabel(MessageUtils.getLocalizedMessage("analysis.dialog.chain.label.tokenizer")), c); |
| + |
| + String tokenizer = analyzer.getTokenizerFactory().getClass().getName(); |
| + JTextField tokenizerTF = new JTextField(tokenizer); |
| + tokenizerTF.setColumns(30); |
| + tokenizerTF.setEditable(false); |
| + tokenizerTF.setPreferredSize(new Dimension(300, 25)); |
| + tokenizerTF.setBorder(BorderFactory.createLineBorder(Color.gray)); |
| + c.gridx = 1; |
| + c.gridy = 1; |
| + c.weightx = 0.5; |
| + c.weighty = 0.1; |
| + panel.add(tokenizerTF, c); |
| + |
| + c.gridx = 0; |
| + c.gridy = 2; |
| + c.weightx = 0.1; |
| + c.weighty = 0.5; |
| + panel.add(new JLabel(MessageUtils.getLocalizedMessage("analysis.dialog.chain.label.tokenfilters")), c); |
| + |
| + String[] tokenFilters = analyzer.getTokenFilterFactories().stream().map(f -> f.getClass().getName()).toArray(String[]::new); |
| + JList<String> tokenFilterList = new JList<>(tokenFilters); |
| + tokenFilterList.setVisibleRowCount(tokenFilters.length == 0 ? 1 : Math.min(tokenFilters.length, 5)); |
| + tokenFilterList.setMinimumSize(new Dimension(300, 25)); |
| + c.gridx = 1; |
| + c.gridy = 2; |
| + c.weightx = 0.5; |
| + c.weighty = 0.5; |
| + panel.add(new JScrollPane(tokenFilterList), c); |
| + |
| + return panel; |
| + } |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditFiltersDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditFiltersDialogFactory.java |
| new file mode 100644 |
| index 00000000000..5a964d68397 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditFiltersDialogFactory.java |
| @@ -0,0 +1,303 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components.dialog.analysis; |
| + |
| +import javax.swing.BorderFactory; |
| +import javax.swing.JButton; |
| +import javax.swing.JDialog; |
| +import javax.swing.JLabel; |
| +import javax.swing.JPanel; |
| +import javax.swing.JScrollPane; |
| +import javax.swing.JTable; |
| +import javax.swing.ListSelectionModel; |
| +import javax.swing.table.TableCellRenderer; |
| +import java.awt.BorderLayout; |
| +import java.awt.Component; |
| +import java.awt.Dialog; |
| +import java.awt.Dimension; |
| +import java.awt.FlowLayout; |
| +import java.awt.Window; |
| +import java.awt.event.MouseAdapter; |
| +import java.awt.event.MouseEvent; |
| +import java.io.IOException; |
| +import java.util.ArrayList; |
| +import java.util.Collections; |
| +import java.util.List; |
| +import java.util.Map; |
| + |
| +import org.apache.lucene.luke.app.desktop.Preferences; |
| +import org.apache.lucene.luke.app.desktop.PreferencesFactory; |
| +import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry; |
| +import org.apache.lucene.luke.app.desktop.components.TableColumnInfo; |
| +import org.apache.lucene.luke.app.desktop.components.TableModelBase; |
| +import org.apache.lucene.luke.app.desktop.components.fragments.analysis.CustomAnalyzerPanelOperator; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.TableUtils; |
| +import org.apache.lucene.luke.app.desktop.util.lang.Callable; |
| + |
| +/** Factory of edit filters dialog */ |
| +public final class EditFiltersDialogFactory implements DialogOpener.DialogFactory { |
| + |
| + private static EditFiltersDialogFactory instance; |
| + |
| + private final Preferences prefs; |
| + |
| + private final ComponentOperatorRegistry operatorRegistry; |
| + |
| + private final EditParamsDialogFactory editParamsDialogFactory; |
| + |
| + private final JLabel targetLbl = new JLabel(); |
| + |
| + private final JTable filtersTable = new JTable(); |
| + |
| + private final ListenerFunctions listeners = new ListenerFunctions(); |
| + |
| + private final FiltersTableMouseListener tableListener = new FiltersTableMouseListener(); |
| + |
| + private JDialog dialog; |
| + |
| + private List<String> selectedFilters; |
| + |
| + private Callable callback; |
| + |
| + private EditFiltersMode mode; |
| + |
| + public synchronized static EditFiltersDialogFactory getInstance() throws IOException { |
| + if (instance == null) { |
| + instance = new EditFiltersDialogFactory(); |
| + } |
| + return instance; |
| + } |
| + |
| + private EditFiltersDialogFactory() throws IOException { |
| + this.prefs = PreferencesFactory.getInstance(); |
| + this.operatorRegistry = ComponentOperatorRegistry.getInstance(); |
| + this.editParamsDialogFactory = EditParamsDialogFactory.getInstance(); |
| + } |
| + |
| + public void setSelectedFilters(List<String> selectedFilters) { |
| + this.selectedFilters = selectedFilters; |
| + } |
| + |
| + public void setCallback(Callable callback) { |
| + this.callback = callback; |
| + } |
| + |
| + public void setMode(EditFiltersMode mode) { |
| + this.mode = mode; |
| + } |
| + |
| + @Override |
| + public JDialog create(Window owner, String title, int width, int height) { |
| + dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL); |
| + dialog.add(content()); |
| + dialog.setSize(new Dimension(width, height)); |
| + dialog.setLocationRelativeTo(owner); |
| + dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor()); |
| + return dialog; |
| + } |
| + |
| + private JPanel content() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); |
| + |
| + JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 10)); |
| + header.setOpaque(false); |
| + header.add(new JLabel(MessageUtils.getLocalizedMessage("analysis.dialog.hint.edit_param"))); |
| + header.add(targetLbl); |
| + panel.add(header, BorderLayout.PAGE_START); |
| + |
| + TableUtils.setupTable(filtersTable, ListSelectionModel.SINGLE_SELECTION, new FiltersTableModel(selectedFilters), tableListener, |
| + FiltersTableModel.Column.DELETE.getColumnWidth(), |
| + FiltersTableModel.Column.ORDER.getColumnWidth()); |
| + filtersTable.setShowGrid(true); |
| + filtersTable.getColumnModel().getColumn(FiltersTableModel.Column.TYPE.getIndex()).setCellRenderer(new TypeCellRenderer()); |
| + panel.add(new JScrollPane(filtersTable), BorderLayout.CENTER); |
| + |
| + JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING, 10, 5)); |
| + footer.setOpaque(false); |
| + JButton okBtn = new JButton(MessageUtils.getLocalizedMessage("button.ok")); |
| + okBtn.addActionListener(e -> { |
| + List<Integer> deletedIndexes = new ArrayList<>(); |
| + for (int i = 0; i < filtersTable.getRowCount(); i++) { |
| + boolean deleted = (boolean) filtersTable.getValueAt(i, FiltersTableModel.Column.DELETE.getIndex()); |
| + if (deleted) { |
| + deletedIndexes.add(i); |
| + } |
| + } |
| + operatorRegistry.get(CustomAnalyzerPanelOperator.class).ifPresent(operator -> { |
| + switch (mode) { |
| + case CHARFILTER: |
| + operator.updateCharFilters(deletedIndexes); |
| + break; |
| + case TOKENFILTER: |
| + operator.updateTokenFilters(deletedIndexes); |
| + break; |
| + } |
| + }); |
| + callback.call(); |
| + dialog.dispose(); |
| + }); |
| + footer.add(okBtn); |
| + JButton cancelBtn = new JButton(MessageUtils.getLocalizedMessage("button.cancel")); |
| + cancelBtn.addActionListener(e -> dialog.dispose()); |
| + footer.add(cancelBtn); |
| + panel.add(footer, BorderLayout.PAGE_END); |
| + |
| + return panel; |
| + } |
| + |
| + private class ListenerFunctions { |
| + |
| + void showEditParamsDialog(MouseEvent e) { |
| + if (e.getClickCount() != 2 || e.isConsumed()) { |
| + return; |
| + } |
| + int selectedIndex = filtersTable.rowAtPoint(e.getPoint()); |
| + if (selectedIndex < 0 || selectedIndex >= selectedFilters.size()) { |
| + return; |
| + } |
| + |
| + switch (mode) { |
| + case CHARFILTER: |
| + showEditParamsCharFilterDialog(selectedIndex); |
| + break; |
| + case TOKENFILTER: |
| + showEditParamsTokenFilterDialog(selectedIndex); |
| + break; |
| + default: |
| + } |
| + } |
| + |
| + private void showEditParamsCharFilterDialog(int selectedIndex) { |
| + int targetIndex = filtersTable.getSelectedRow(); |
| + String selectedItem = (String) filtersTable.getValueAt(selectedIndex, FiltersTableModel.Column.TYPE.getIndex()); |
| + Map<String, String> params = operatorRegistry.get(CustomAnalyzerPanelOperator.class).map(operator -> operator.getCharFilterParams(targetIndex)).orElse(Collections.emptyMap()); |
| + new DialogOpener<>(editParamsDialogFactory).open(dialog, MessageUtils.getLocalizedMessage("analysis.dialog.title.char_filter_params"), 400, 300, |
| + factory -> { |
| + factory.setMode(EditParamsMode.CHARFILTER); |
| + factory.setTargetIndex(targetIndex); |
| + factory.setTarget(selectedItem); |
| + factory.setParams(params); |
| + }); |
| + } |
| + |
| + private void showEditParamsTokenFilterDialog(int selectedIndex) { |
| + int targetIndex = filtersTable.getSelectedRow(); |
| + String selectedItem = (String) filtersTable.getValueAt(selectedIndex, FiltersTableModel.Column.TYPE.getIndex()); |
| + Map<String, String> params = operatorRegistry.get(CustomAnalyzerPanelOperator.class).map(operator -> operator.getTokenFilterParams(targetIndex)).orElse(Collections.emptyMap()); |
| + new DialogOpener<>(editParamsDialogFactory).open(dialog, MessageUtils.getLocalizedMessage("analysis.dialog.title.char_filter_params"), 400, 300, |
| + factory -> { |
| + factory.setMode(EditParamsMode.TOKENFILTER); |
| + factory.setTargetIndex(targetIndex); |
| + factory.setTarget(selectedItem); |
| + factory.setParams(params); |
| + }); |
| + } |
| + } |
| + |
| + private class FiltersTableMouseListener extends MouseAdapter { |
| + @Override |
| + public void mouseClicked(MouseEvent e) { |
| + listeners.showEditParamsDialog(e); |
| + } |
| + } |
| + |
| + static final class FiltersTableModel extends TableModelBase<FiltersTableModel.Column> { |
| + |
| + enum Column implements TableColumnInfo { |
| + DELETE("Delete", 0, Boolean.class, 50), |
| + ORDER("Order", 1, Integer.class, 50), |
| + TYPE("Factory class", 2, String.class, Integer.MAX_VALUE); |
| + |
| + private final String colName; |
| + private final int index; |
| + private final Class<?> type; |
| + private final int width; |
| + |
| + Column(String colName, int index, Class<?> type, int width) { |
| + this.colName = colName; |
| + this.index = index; |
| + this.type = type; |
| + this.width = width; |
| + } |
| + |
| + @Override |
| + public String getColName() { |
| + return colName; |
| + } |
| + |
| + @Override |
| + public int getIndex() { |
| + return index; |
| + } |
| + |
| + @Override |
| + public Class<?> getType() { |
| + return type; |
| + } |
| + |
| + @Override |
| + public int getColumnWidth() { |
| + return width; |
| + } |
| + } |
| + |
| + FiltersTableModel() { |
| + super(); |
| + } |
| + |
| + FiltersTableModel(List<String> selectedFilters) { |
| + super(selectedFilters.size()); |
| + for (int i = 0; i < selectedFilters.size(); i++) { |
| + data[i][Column.DELETE.getIndex()] = false; |
| + data[i][Column.ORDER.getIndex()] = i + 1; |
| + data[i][Column.TYPE.getIndex()] = selectedFilters.get(i); |
| + } |
| + } |
| + |
| + @Override |
| + public boolean isCellEditable(int rowIndex, int columnIndex) { |
| + return columnIndex == Column.DELETE.getIndex(); |
| + } |
| + |
| + @Override |
| + public void setValueAt(Object value, int rowIndex, int columnIndex) { |
| + data[rowIndex][columnIndex] = value; |
| + } |
| + |
| + @Override |
| + protected Column[] columnInfos() { |
| + return Column.values(); |
| + } |
| + } |
| + |
| + static final class TypeCellRenderer implements TableCellRenderer { |
| + |
| + @Override |
| + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { |
| + String[] tmp = ((String) value).split("\\."); |
| + String type = tmp[tmp.length - 1]; |
| + return new JLabel(type); |
| + } |
| + |
| + } |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditFiltersMode.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditFiltersMode.java |
| new file mode 100644 |
| index 00000000000..d5edd8b505e |
| --- /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 00000000000..f9a30da8cd2 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditParamsDialogFactory.java |
| @@ -0,0 +1,254 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components.dialog.analysis; |
| + |
| +import javax.swing.BorderFactory; |
| +import javax.swing.JButton; |
| +import javax.swing.JDialog; |
| +import javax.swing.JLabel; |
| +import javax.swing.JPanel; |
| +import javax.swing.JScrollPane; |
| +import javax.swing.JTable; |
| +import javax.swing.ListSelectionModel; |
| +import java.awt.BorderLayout; |
| +import java.awt.Dialog; |
| +import java.awt.Dimension; |
| +import java.awt.FlowLayout; |
| +import java.awt.Window; |
| +import java.io.IOException; |
| +import java.util.ArrayList; |
| +import java.util.HashMap; |
| +import java.util.List; |
| +import java.util.Map; |
| +import java.util.Objects; |
| + |
| +import org.apache.lucene.luke.app.desktop.Preferences; |
| +import org.apache.lucene.luke.app.desktop.PreferencesFactory; |
| +import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry; |
| +import org.apache.lucene.luke.app.desktop.components.TableColumnInfo; |
| +import org.apache.lucene.luke.app.desktop.components.TableModelBase; |
| +import org.apache.lucene.luke.app.desktop.components.fragments.analysis.CustomAnalyzerPanelOperator; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.TableUtils; |
| +import org.apache.lucene.luke.app.desktop.util.lang.Callable; |
| + |
| +/** Factory of edit parameters dialog */ |
| +public final class EditParamsDialogFactory implements DialogOpener.DialogFactory { |
| + |
| + private static EditParamsDialogFactory instance; |
| + |
| + private final Preferences prefs; |
| + |
| + private final ComponentOperatorRegistry operatorRegistry; |
| + |
| + private final JTable paramsTable = new JTable(); |
| + |
| + private JDialog dialog; |
| + |
| + private EditParamsMode mode; |
| + |
| + private String target; |
| + |
| + private int targetIndex; |
| + |
| + private Map<String, String> params = new HashMap<>(); |
| + |
| + private Callable callback; |
| + |
| + public synchronized static EditParamsDialogFactory getInstance() throws IOException { |
| + if (instance == null) { |
| + instance = new EditParamsDialogFactory(); |
| + } |
| + return instance; |
| + } |
| + |
| + private EditParamsDialogFactory() throws IOException { |
| + this.prefs = PreferencesFactory.getInstance(); |
| + this.operatorRegistry = ComponentOperatorRegistry.getInstance(); |
| + } |
| + |
| + public void setMode(EditParamsMode mode) { |
| + this.mode = mode; |
| + } |
| + |
| + public void setTarget(String target) { |
| + this.target = target; |
| + } |
| + |
| + public void setTargetIndex(int targetIndex) { |
| + this.targetIndex = targetIndex; |
| + } |
| + |
| + public void setParams(Map<String, String> params) { |
| + this.params.putAll(params); |
| + } |
| + |
| + public void setCallback(Callable callback) { |
| + this.callback = callback; |
| + } |
| + |
| + @Override |
| + public JDialog create(Window owner, String title, int width, int height) { |
| + dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL); |
| + dialog.add(content()); |
| + dialog.setSize(new Dimension(width, height)); |
| + dialog.setLocationRelativeTo(owner); |
| + dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor()); |
| + return dialog; |
| + } |
| + |
| + private JPanel content() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); |
| + |
| + JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 10)); |
| + header.setOpaque(false); |
| + header.add(new JLabel("Parameters for:")); |
| + String[] tmp = target.split("\\."); |
| + JLabel targetLbl = new JLabel(tmp[tmp.length - 1]); |
| + header.add(targetLbl); |
| + panel.add(header, BorderLayout.PAGE_START); |
| + |
| + TableUtils.setupTable(paramsTable, ListSelectionModel.SINGLE_SELECTION, new ParamsTableModel(params), null, |
| + ParamsTableModel.Column.DELETE.getColumnWidth(), |
| + ParamsTableModel.Column.NAME.getColumnWidth()); |
| + paramsTable.setShowGrid(true); |
| + panel.add(new JScrollPane(paramsTable), BorderLayout.CENTER); |
| + |
| + JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING, 10, 5)); |
| + footer.setOpaque(false); |
| + JButton okBtn = new JButton(MessageUtils.getLocalizedMessage("button.ok")); |
| + okBtn.addActionListener(e -> { |
| + Map<String, String> params = new HashMap<>(); |
| + for (int i = 0; i < paramsTable.getRowCount(); i++) { |
| + boolean deleted = (boolean) paramsTable.getValueAt(i, ParamsTableModel.Column.DELETE.getIndex()); |
| + String name = (String) paramsTable.getValueAt(i, ParamsTableModel.Column.NAME.getIndex()); |
| + String value = (String) paramsTable.getValueAt(i, ParamsTableModel.Column.VALUE.getIndex()); |
| + if (deleted || Objects.isNull(name) || name.equals("") || Objects.isNull(value) || value.equals("")) { |
| + continue; |
| + } |
| + params.put(name, value); |
| + } |
| + updateTargetParams(params); |
| + callback.call(); |
| + this.params.clear(); |
| + dialog.dispose(); |
| + }); |
| + footer.add(okBtn); |
| + JButton cancelBtn = new JButton(MessageUtils.getLocalizedMessage("button.cancel")); |
| + cancelBtn.addActionListener(e -> { |
| + this.params.clear(); |
| + dialog.dispose(); |
| + }); |
| + footer.add(cancelBtn); |
| + panel.add(footer, BorderLayout.PAGE_END); |
| + |
| + return panel; |
| + } |
| + |
| + private void updateTargetParams(Map<String, String> params) { |
| + operatorRegistry.get(CustomAnalyzerPanelOperator.class).ifPresent(operator -> { |
| + switch (mode) { |
| + case CHARFILTER: |
| + operator.updateCharFilterParams(targetIndex, params); |
| + break; |
| + case TOKENIZER: |
| + operator.updateTokenizerParams(params); |
| + break; |
| + case TOKENFILTER: |
| + operator.updateTokenFilterParams(targetIndex, params); |
| + break; |
| + } |
| + }); |
| + } |
| + |
| + static final class ParamsTableModel extends TableModelBase<ParamsTableModel.Column> { |
| + |
| + enum Column implements TableColumnInfo { |
| + DELETE("Delete", 0, Boolean.class, 50), |
| + NAME("Name", 1, String.class, 150), |
| + VALUE("Value", 2, String.class, Integer.MAX_VALUE); |
| + |
| + private final String colName; |
| + private final int index; |
| + private final Class<?> type; |
| + private final int width; |
| + |
| + Column(String colName, int index, Class<?> type, int width) { |
| + this.colName = colName; |
| + this.index = index; |
| + this.type = type; |
| + this.width = width; |
| + } |
| + |
| + @Override |
| + public String getColName() { |
| + return colName; |
| + } |
| + |
| + @Override |
| + public int getIndex() { |
| + return index; |
| + } |
| + |
| + @Override |
| + public Class<?> getType() { |
| + return type; |
| + } |
| + |
| + @Override |
| + public int getColumnWidth() { |
| + return width; |
| + } |
| + |
| + } |
| + |
| + private static final int PARAM_SIZE = 20; |
| + |
| + ParamsTableModel(Map<String, String> params) { |
| + super(PARAM_SIZE); |
| + List<String> keys = new ArrayList<>(params.keySet()); |
| + for (int i = 0; i < keys.size(); i++) { |
| + data[i][Column.NAME.getIndex()] = keys.get(i); |
| + data[i][Column.VALUE.getIndex()] = params.get(keys.get(i)); |
| + } |
| + for (int i = 0; i < data.length; i++) { |
| + data[i][Column.DELETE.getIndex()] = false; |
| + } |
| + } |
| + |
| + @Override |
| + public boolean isCellEditable(int rowIndex, int columnIndex) { |
| + return true; |
| + } |
| + |
| + @Override |
| + public void setValueAt(Object value, int rowIndex, int columnIndex) { |
| + data[rowIndex][columnIndex] = value; |
| + } |
| + |
| + @Override |
| + protected Column[] columnInfos() { |
| + return Column.values(); |
| + } |
| + } |
| + |
| +} |
| + |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditParamsMode.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/EditParamsMode.java |
| new file mode 100644 |
| index 00000000000..8e76879dc22 |
| --- /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 00000000000..4112699754f |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/TokenAttributeDialogFactory.java |
| @@ -0,0 +1,196 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components.dialog.analysis; |
| + |
| +import javax.swing.BorderFactory; |
| +import javax.swing.JButton; |
| +import javax.swing.JDialog; |
| +import javax.swing.JLabel; |
| +import javax.swing.JPanel; |
| +import javax.swing.JScrollPane; |
| +import javax.swing.JTable; |
| +import javax.swing.ListSelectionModel; |
| +import java.awt.BorderLayout; |
| +import java.awt.Dialog; |
| +import java.awt.Dimension; |
| +import java.awt.FlowLayout; |
| +import java.awt.Window; |
| +import java.io.IOException; |
| +import java.util.List; |
| +import java.util.stream.Collectors; |
| + |
| +import org.apache.lucene.luke.app.desktop.Preferences; |
| +import org.apache.lucene.luke.app.desktop.PreferencesFactory; |
| +import org.apache.lucene.luke.app.desktop.components.TableColumnInfo; |
| +import org.apache.lucene.luke.app.desktop.components.TableModelBase; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.TableUtils; |
| +import org.apache.lucene.luke.models.analysis.Analysis; |
| + |
| +/** Factory of token attribute dialog */ |
| +public final class TokenAttributeDialogFactory implements DialogOpener.DialogFactory { |
| + |
| + private static TokenAttributeDialogFactory instance; |
| + |
| + private final Preferences prefs; |
| + |
| + private final JTable attributesTable = new JTable(); |
| + |
| + private JDialog dialog; |
| + |
| + private String term; |
| + |
| + private List<Analysis.TokenAttribute> attributes; |
| + |
| + public synchronized static TokenAttributeDialogFactory getInstance() throws IOException { |
| + if (instance == null) { |
| + instance = new TokenAttributeDialogFactory(); |
| + } |
| + return instance; |
| + } |
| + |
| + private TokenAttributeDialogFactory() throws IOException { |
| + this.prefs = PreferencesFactory.getInstance(); |
| + } |
| + |
| + public void setTerm(String term) { |
| + this.term = term; |
| + } |
| + |
| + public void setAttributes(List<Analysis.TokenAttribute> attributes) { |
| + this.attributes = attributes; |
| + } |
| + |
| + @Override |
| + public JDialog create(Window owner, String title, int width, int height) { |
| + dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL); |
| + dialog.add(content()); |
| + dialog.setSize(new Dimension(width, height)); |
| + dialog.setLocationRelativeTo(owner); |
| + dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor()); |
| + return dialog; |
| + } |
| + |
| + private JPanel content() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); |
| + |
| + JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + header.setOpaque(false); |
| + header.add(new JLabel("All token attributes for:")); |
| + header.add(new JLabel(term)); |
| + panel.add(header, BorderLayout.PAGE_START); |
| + |
| + List<TokenAttValue> attrValues = attributes.stream() |
| + .flatMap(att -> att.getAttValues().entrySet().stream().map(e -> TokenAttValue.of(att.getAttClass(), e.getKey(), e.getValue()))) |
| + .collect(Collectors.toList()); |
| + TableUtils.setupTable(attributesTable, ListSelectionModel.SINGLE_SELECTION, new AttributeTableModel(attrValues), null); |
| + panel.add(new JScrollPane(attributesTable), BorderLayout.CENTER); |
| + |
| + JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING)); |
| + footer.setOpaque(false); |
| + JButton okBtn = new JButton(MessageUtils.getLocalizedMessage("button.ok")); |
| + okBtn.addActionListener(e -> dialog.dispose()); |
| + footer.add(okBtn); |
| + panel.add(footer, BorderLayout.PAGE_END); |
| + |
| + return panel; |
| + } |
| + |
| + static final class AttributeTableModel extends TableModelBase<AttributeTableModel.Column> { |
| + |
| + enum Column implements TableColumnInfo { |
| + |
| + ATTR("Attribute", 0, String.class), |
| + NAME("Name", 1, String.class), |
| + VALUE("Value", 2, String.class); |
| + |
| + private final String colName; |
| + private final int index; |
| + private final Class<?> type; |
| + |
| + Column(String colName, int index, Class<?> type) { |
| + this.colName = colName; |
| + this.index = index; |
| + this.type = type; |
| + } |
| + |
| + @Override |
| + public String getColName() { |
| + return colName; |
| + } |
| + |
| + @Override |
| + public int getIndex() { |
| + return index; |
| + } |
| + |
| + @Override |
| + public Class<?> getType() { |
| + return type; |
| + } |
| + } |
| + |
| + AttributeTableModel(List<TokenAttValue> attrValues) { |
| + super(attrValues.size()); |
| + for (int i = 0; i < attrValues.size(); i++) { |
| + TokenAttValue attrValue = attrValues.get(i); |
| + data[i][Column.ATTR.getIndex()] = attrValue.getAttClass(); |
| + data[i][Column.NAME.getIndex()] = attrValue.getName(); |
| + data[i][Column.VALUE.getIndex()] = attrValue.getValue(); |
| + } |
| + } |
| + |
| + @Override |
| + protected Column[] columnInfos() { |
| + return Column.values(); |
| + } |
| + } |
| + |
| + static final class TokenAttValue { |
| + private String attClass; |
| + private String name; |
| + private String value; |
| + |
| + public static TokenAttValue of(String attClass, String name, String value) { |
| + TokenAttValue attValue = new TokenAttValue(); |
| + attValue.attClass = attClass; |
| + attValue.name = name; |
| + attValue.value = value; |
| + return attValue; |
| + } |
| + |
| + private TokenAttValue() { |
| + } |
| + |
| + String getAttClass() { |
| + return attClass; |
| + } |
| + |
| + String getName() { |
| + return name; |
| + } |
| + |
| + String getValue() { |
| + return value; |
| + } |
| + } |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/analysis/package-info.java |
| new file mode 100644 |
| index 00000000000..bd3419bd66f |
| --- /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 00000000000..0bbeb3eb6f5 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/AddDocumentDialogFactory.java |
| @@ -0,0 +1,593 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components.dialog.documents; |
| + |
| +import javax.swing.BorderFactory; |
| +import javax.swing.BoxLayout; |
| +import javax.swing.DefaultCellEditor; |
| +import javax.swing.JButton; |
| +import javax.swing.JComboBox; |
| +import javax.swing.JComponent; |
| +import javax.swing.JDialog; |
| +import javax.swing.JLabel; |
| +import javax.swing.JPanel; |
| +import javax.swing.JScrollPane; |
| +import javax.swing.JTable; |
| +import javax.swing.JTextArea; |
| +import javax.swing.ListSelectionModel; |
| +import javax.swing.UIManager; |
| +import javax.swing.table.JTableHeader; |
| +import javax.swing.table.TableCellRenderer; |
| +import java.awt.BorderLayout; |
| +import java.awt.Color; |
| +import java.awt.Component; |
| +import java.awt.Dialog; |
| +import java.awt.Dimension; |
| +import java.awt.FlowLayout; |
| +import java.awt.GridLayout; |
| +import java.awt.Insets; |
| +import java.awt.Window; |
| +import java.awt.event.ActionEvent; |
| +import java.awt.event.MouseAdapter; |
| +import java.awt.event.MouseEvent; |
| +import java.io.IOException; |
| +import java.lang.invoke.MethodHandles; |
| +import java.lang.reflect.Constructor; |
| +import java.util.List; |
| +import java.util.stream.Collectors; |
| +import java.util.stream.IntStream; |
| + |
| +import org.apache.logging.log4j.Logger; |
| +import org.apache.lucene.analysis.Analyzer; |
| +import org.apache.lucene.analysis.standard.StandardAnalyzer; |
| +import org.apache.lucene.document.Document; |
| +import org.apache.lucene.document.DoublePoint; |
| +import org.apache.lucene.document.Field; |
| +import org.apache.lucene.document.FloatPoint; |
| +import org.apache.lucene.document.IntPoint; |
| +import org.apache.lucene.document.LongPoint; |
| +import org.apache.lucene.document.NumericDocValuesField; |
| +import org.apache.lucene.document.SortedDocValuesField; |
| +import org.apache.lucene.document.SortedNumericDocValuesField; |
| +import org.apache.lucene.document.SortedSetDocValuesField; |
| +import org.apache.lucene.document.StoredField; |
| +import org.apache.lucene.document.StringField; |
| +import org.apache.lucene.document.TextField; |
| +import org.apache.lucene.index.IndexableField; |
| +import org.apache.lucene.index.IndexableFieldType; |
| +import org.apache.lucene.luke.app.IndexHandler; |
| +import org.apache.lucene.luke.app.IndexObserver; |
| +import org.apache.lucene.luke.app.LukeState; |
| +import org.apache.lucene.luke.app.desktop.Preferences; |
| +import org.apache.lucene.luke.app.desktop.PreferencesFactory; |
| +import org.apache.lucene.luke.app.desktop.components.AnalysisTabOperator; |
| +import org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry; |
| +import org.apache.lucene.luke.app.desktop.components.DocumentsTabOperator; |
| +import org.apache.lucene.luke.app.desktop.components.TabSwitcherProxy; |
| +import org.apache.lucene.luke.app.desktop.components.TabbedPaneProvider; |
| +import org.apache.lucene.luke.app.desktop.components.TableColumnInfo; |
| +import org.apache.lucene.luke.app.desktop.components.TableModelBase; |
| +import org.apache.lucene.luke.app.desktop.components.dialog.HelpDialogFactory; |
| +import org.apache.lucene.luke.app.desktop.dto.documents.NewField; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.FontUtils; |
| +import org.apache.lucene.luke.app.desktop.util.HelpHeaderRenderer; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.NumericUtils; |
| +import org.apache.lucene.luke.app.desktop.util.StringUtils; |
| +import org.apache.lucene.luke.app.desktop.util.TableUtils; |
| +import org.apache.lucene.luke.models.LukeException; |
| +import org.apache.lucene.luke.models.tools.IndexTools; |
| +import org.apache.lucene.luke.models.tools.IndexToolsFactory; |
| +import org.apache.lucene.luke.util.LoggerFactory; |
| +import org.apache.lucene.util.BytesRef; |
| + |
| +/** Factory of add document dialog */ |
| +public final class AddDocumentDialogFactory implements DialogOpener.DialogFactory, AddDocumentDialogOperator { |
| + |
| + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| + |
| + private static AddDocumentDialogFactory instance; |
| + |
| + private final static int ROW_COUNT = 50; |
| + |
| + private final Preferences prefs; |
| + |
| + private final IndexHandler indexHandler; |
| + |
| + private final IndexToolsFactory toolsFactory = new IndexToolsFactory(); |
| + |
| + private final TabSwitcherProxy tabSwitcher; |
| + |
| + private final ComponentOperatorRegistry operatorRegistry; |
| + |
| + private final IndexOptionsDialogFactory indexOptionsDialogFactory; |
| + |
| + private final HelpDialogFactory helpDialogFactory; |
| + |
| + private final ListenerFunctions listeners = new ListenerFunctions(); |
| + |
| + private final JLabel analyzerNameLbl = new JLabel(StandardAnalyzer.class.getName()); |
| + |
| + private final List<NewField> newFieldList; |
| + |
| + private final JButton addBtn = new JButton(); |
| + |
| + private final JButton closeBtn = new JButton(); |
| + |
| + private final JTextArea infoTA = new JTextArea(); |
| + |
| + private IndexTools toolsModel; |
| + |
| + private JDialog dialog; |
| + |
| + public synchronized static AddDocumentDialogFactory getInstance() throws IOException { |
| + if (instance == null) { |
| + instance = new AddDocumentDialogFactory(); |
| + } |
| + return instance; |
| + } |
| + |
| + private AddDocumentDialogFactory() throws IOException { |
| + this.prefs = PreferencesFactory.getInstance(); |
| + this.indexHandler = IndexHandler.getInstance(); |
| + this.tabSwitcher = TabSwitcherProxy.getInstance(); |
| + this.operatorRegistry = ComponentOperatorRegistry.getInstance(); |
| + this.indexOptionsDialogFactory = IndexOptionsDialogFactory.getInstance(); |
| + this.helpDialogFactory = HelpDialogFactory.getInstance(); |
| + this.newFieldList = IntStream.range(0, ROW_COUNT).mapToObj(i -> NewField.newInstance()).collect(Collectors.toList()); |
| + |
| + operatorRegistry.register(AddDocumentDialogOperator.class, this); |
| + indexHandler.addObserver(new Observer()); |
| + |
| + initialize(); |
| + } |
| + |
| + private void initialize() { |
| + addBtn.setText(MessageUtils.getLocalizedMessage("add_document.button.add")); |
| + addBtn.setMargin(new Insets(3, 3, 3, 3)); |
| + addBtn.setEnabled(true); |
| + addBtn.addActionListener(listeners::addDocument); |
| + |
| + closeBtn.setText(MessageUtils.getLocalizedMessage("button.cancel")); |
| + closeBtn.setMargin(new Insets(3, 3, 3, 3)); |
| + closeBtn.addActionListener(e -> dialog.dispose()); |
| + |
| + infoTA.setRows(3); |
| + infoTA.setLineWrap(true); |
| + infoTA.setEditable(false); |
| + infoTA.setText(MessageUtils.getLocalizedMessage("add_document.info")); |
| + infoTA.setForeground(Color.gray); |
| + } |
| + |
| + @Override |
| + public JDialog create(Window owner, String title, int width, int height) { |
| + dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL); |
| + dialog.add(content()); |
| + dialog.setSize(new Dimension(width, height)); |
| + dialog.setLocationRelativeTo(owner); |
| + dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor()); |
| + return dialog; |
| + } |
| + |
| + private JPanel content() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); |
| + panel.add(header(), BorderLayout.PAGE_START); |
| + panel.add(center(), BorderLayout.CENTER); |
| + panel.add(footer(), BorderLayout.PAGE_END); |
| + return panel; |
| + } |
| + |
| + private JPanel header() { |
| + JPanel panel = new JPanel(); |
| + panel.setOpaque(false); |
| + panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); |
| + |
| + JPanel analyzerHeader = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 10)); |
| + analyzerHeader.setOpaque(false); |
| + analyzerHeader.add(new JLabel(MessageUtils.getLocalizedMessage("add_document.label.analyzer"))); |
| + analyzerHeader.add(analyzerNameLbl); |
| + JLabel changeLbl = new JLabel(MessageUtils.getLocalizedMessage("add_document.hyperlink.change")); |
| + changeLbl.addMouseListener(new MouseAdapter() { |
| + @Override |
| + public void mouseClicked(MouseEvent e) { |
| + dialog.dispose(); |
| + tabSwitcher.switchTab(TabbedPaneProvider.Tab.ANALYZER); |
| + } |
| + }); |
| + analyzerHeader.add(FontUtils.toLinkText(changeLbl)); |
| + panel.add(analyzerHeader); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel center() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); |
| + |
| + JPanel tableHeader = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 5)); |
| + tableHeader.setOpaque(false); |
| + tableHeader.add(new JLabel(MessageUtils.getLocalizedMessage("add_document.label.fields"))); |
| + panel.add(tableHeader, BorderLayout.PAGE_START); |
| + |
| + JScrollPane scrollPane = new JScrollPane(fieldsTable()); |
| + scrollPane.setOpaque(false); |
| + scrollPane.getViewport().setOpaque(false); |
| + panel.add(scrollPane, BorderLayout.CENTER); |
| + |
| + JPanel tableFooter = new JPanel(new FlowLayout(FlowLayout.TRAILING, 10, 5)); |
| + tableFooter.setOpaque(false); |
| + addBtn.setEnabled(true); |
| + tableFooter.add(addBtn); |
| + tableFooter.add(closeBtn); |
| + panel.add(tableFooter, BorderLayout.PAGE_END); |
| + |
| + return panel; |
| + } |
| + |
| + private JTable fieldsTable() { |
| + JTable fieldsTable = new JTable(); |
| + TableUtils.setupTable(fieldsTable, ListSelectionModel.SINGLE_SELECTION, new FieldsTableModel(newFieldList), null, 30, 150, 120, 80); |
| + fieldsTable.setShowGrid(true); |
| + JComboBox<Class<? extends IndexableField>> typesCombo = new JComboBox<>(presetFieldClasses); |
| + typesCombo.setRenderer((list, value, index, isSelected, cellHasFocus) -> new JLabel(value.getSimpleName())); |
| + fieldsTable.getColumnModel().getColumn(FieldsTableModel.Column.TYPE.getIndex()).setCellEditor(new DefaultCellEditor(typesCombo)); |
| + for (int i = 0; i < fieldsTable.getModel().getRowCount(); i++) { |
| + fieldsTable.getModel().setValueAt(TextField.class, i, FieldsTableModel.Column.TYPE.getIndex()); |
| + } |
| + fieldsTable.getColumnModel().getColumn(FieldsTableModel.Column.TYPE.getIndex()).setHeaderRenderer( |
| + new HelpHeaderRenderer( |
| + "About Type", "Select Field Class:", |
| + createTypeHelpDialog(), helpDialogFactory, dialog)); |
| + fieldsTable.getColumnModel().getColumn(FieldsTableModel.Column.TYPE.getIndex()).setCellRenderer(new TypeCellRenderer()); |
| + fieldsTable.getColumnModel().getColumn(FieldsTableModel.Column.OPTIONS.getIndex()).setCellRenderer(new OptionsCellRenderer(dialog, indexOptionsDialogFactory, newFieldList)); |
| + return fieldsTable; |
| + } |
| + |
| + private JComponent createTypeHelpDialog() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + |
| + JTextArea descTA = new JTextArea(); |
| + |
| + JPanel header = new JPanel(); |
| + header.setOpaque(false); |
| + header.setLayout(new BoxLayout(header, BoxLayout.PAGE_AXIS)); |
| + String[] typeList = new String[]{ |
| + "TextField", |
| + "StringField", |
| + "IntPoint", |
| + "LongPoint", |
| + "FloatPoint", |
| + "DoublePoint", |
| + "SortedDocValuesField", |
| + "SortedSetDocValuesField", |
| + "NumericDocValuesField", |
| + "SortedNumericDocValuesField", |
| + "StoredField", |
| + "Field" |
| + }; |
| + JPanel wrapper1 = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + wrapper1.setOpaque(false); |
| + JComboBox<String> typeCombo = new JComboBox<>(typeList); |
| + typeCombo.setSelectedItem(typeList[0]); |
| + typeCombo.addActionListener(e -> { |
| + String selected = (String) typeCombo.getSelectedItem(); |
| + descTA.setText(MessageUtils.getLocalizedMessage("help.fieldtype." + selected)); |
| + }); |
| + wrapper1.add(typeCombo); |
| + header.add(wrapper1); |
| + JPanel wrapper2 = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + wrapper2.setOpaque(false); |
| + wrapper2.add(new JLabel("Brief description and Examples")); |
| + header.add(wrapper2); |
| + panel.add(header, BorderLayout.PAGE_START); |
| + |
| + descTA.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); |
| + descTA.setEditable(false); |
| + descTA.setLineWrap(true); |
| + descTA.setRows(10); |
| + descTA.setText(MessageUtils.getLocalizedMessage("help.fieldtype." + typeList[0])); |
| + JScrollPane scrollPane = new JScrollPane(descTA); |
| + panel.add(scrollPane, BorderLayout.CENTER); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel footer() { |
| + JPanel panel = new JPanel(new GridLayout(1, 1)); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); |
| + |
| + JScrollPane scrollPane = new JScrollPane(infoTA); |
| + scrollPane.setOpaque(false); |
| + scrollPane.getViewport().setOpaque(false); |
| + panel.add(scrollPane); |
| + return panel; |
| + } |
| + |
| + @SuppressWarnings({"unchecked", "rawtypes"}) |
| + private final Class<? extends IndexableField>[] presetFieldClasses = new Class[]{ |
| + TextField.class, StringField.class, |
| + IntPoint.class, LongPoint.class, FloatPoint.class, DoublePoint.class, |
| + SortedDocValuesField.class, SortedSetDocValuesField.class, |
| + NumericDocValuesField.class, SortedNumericDocValuesField.class, |
| + StoredField.class, Field.class |
| + }; |
| + |
| + @Override |
| + public void setAnalyzer(Analyzer analyzer) { |
| + analyzerNameLbl.setText(analyzer.getClass().getName()); |
| + } |
| + |
| + private class ListenerFunctions { |
| + |
| + void addDocument(ActionEvent e) { |
| + List<NewField> validFields = newFieldList.stream() |
| + .filter(nf -> !nf.isDeleted()) |
| + .filter(nf -> !StringUtils.isNullOrEmpty(nf.getName())) |
| + .filter(nf -> !StringUtils.isNullOrEmpty(nf.getValue())) |
| + .collect(Collectors.toList()); |
| + if (validFields.isEmpty()) { |
| + infoTA.setText("Please add one or more fields. Name and Value are both required."); |
| + return; |
| + } |
| + |
| + Document doc = new Document(); |
| + try { |
| + for (NewField nf : validFields) { |
| + doc.add(toIndexableField(nf)); |
| + } |
| + } catch (NumberFormatException ex) { |
| + log.error(ex.getMessage(), e); |
| + throw new LukeException("Invalid value: " + ex.getMessage(), ex); |
| + } catch (Exception ex) { |
| + log.error(ex.getMessage(), e); |
| + throw new LukeException(ex.getMessage(), ex); |
| + } |
| + |
| + addDocument(doc); |
| + log.info("Added document: {}", doc.toString()); |
| + } |
| + |
| + @SuppressWarnings("unchecked") |
| + private IndexableField toIndexableField(NewField nf) throws Exception { |
| + final Constructor<? extends IndexableField> constr; |
| + if (nf.getType().equals(TextField.class) || nf.getType().equals(StringField.class)) { |
| + Field.Store store = nf.isStored() ? Field.Store.YES : Field.Store.NO; |
| + constr = nf.getType().getConstructor(String.class, String.class, Field.Store.class); |
| + return constr.newInstance(nf.getName(), nf.getValue(), store); |
| + } else if (nf.getType().equals(IntPoint.class)) { |
| + constr = nf.getType().getConstructor(String.class, int[].class); |
| + int[] values = NumericUtils.convertToIntArray(nf.getValue(), false); |
| + return constr.newInstance(nf.getName(), values); |
| + } else if (nf.getType().equals(LongPoint.class)) { |
| + constr = nf.getType().getConstructor(String.class, long[].class); |
| + long[] values = NumericUtils.convertToLongArray(nf.getValue(), false); |
| + return constr.newInstance(nf.getName(), values); |
| + } else if (nf.getType().equals(FloatPoint.class)) { |
| + constr = nf.getType().getConstructor(String.class, float[].class); |
| + float[] values = NumericUtils.convertToFloatArray(nf.getValue(), false); |
| + return constr.newInstance(nf.getName(), values); |
| + } else if (nf.getType().equals(DoublePoint.class)) { |
| + constr = nf.getType().getConstructor(String.class, double[].class); |
| + double[] values = NumericUtils.convertToDoubleArray(nf.getValue(), false); |
| + return constr.newInstance(nf.getName(), values); |
| + } else if (nf.getType().equals(SortedDocValuesField.class) || |
| + nf.getType().equals(SortedSetDocValuesField.class)) { |
| + constr = nf.getType().getConstructor(String.class, BytesRef.class); |
| + return constr.newInstance(nf.getName(), new BytesRef(nf.getValue())); |
| + } else if (nf.getType().equals(NumericDocValuesField.class) || |
| + nf.getType().equals(SortedNumericDocValuesField.class)) { |
| + constr = nf.getType().getConstructor(String.class, long.class); |
| + long value = NumericUtils.tryConvertToLongValue(nf.getValue()); |
| + return constr.newInstance(nf.getName(), value); |
| + } else if (nf.getType().equals(StoredField.class)) { |
| + constr = nf.getType().getConstructor(String.class, String.class); |
| + return constr.newInstance(nf.getName(), nf.getValue()); |
| + } else if (nf.getType().equals(Field.class)) { |
| + constr = nf.getType().getConstructor(String.class, String.class, IndexableFieldType.class); |
| + return constr.newInstance(nf.getName(), nf.getValue(), nf.getFieldType()); |
| + } else { |
| + // TODO: unknown field |
| + return new StringField(nf.getName(), nf.getValue(), Field.Store.YES); |
| + } |
| + } |
| + |
| + private void addDocument(Document doc) { |
| + try { |
| + Analyzer analyzer = operatorRegistry.get(AnalysisTabOperator.class) |
| + .map(AnalysisTabOperator::getCurrentAnalyzer) |
| + .orElse(new StandardAnalyzer()); |
| + toolsModel.addDocument(doc, analyzer); |
| + indexHandler.reOpen(); |
| + operatorRegistry.get(DocumentsTabOperator.class).ifPresent(DocumentsTabOperator::displayLatestDoc); |
| + tabSwitcher.switchTab(TabbedPaneProvider.Tab.DOCUMENTS); |
| + infoTA.setText(MessageUtils.getLocalizedMessage("add_document.message.success")); |
| + addBtn.setEnabled(false); |
| + closeBtn.setText(MessageUtils.getLocalizedMessage("button.close")); |
| + } catch (LukeException e) { |
| + infoTA.setText(MessageUtils.getLocalizedMessage("add_document.message.fail")); |
| + throw e; |
| + } catch (Exception e) { |
| + infoTA.setText(MessageUtils.getLocalizedMessage("add_document.message.fail")); |
| + throw new LukeException(e.getMessage(), e); |
| + } |
| + } |
| + |
| + } |
| + |
| + private class Observer implements IndexObserver { |
| + |
| + @Override |
| + public void openIndex(LukeState state) { |
| + toolsModel = toolsFactory.newInstance(state.getIndexReader(), state.useCompound(), state.keepAllCommits()); |
| + } |
| + |
| + @Override |
| + public void closeIndex() { |
| + toolsModel = null; |
| + } |
| + } |
| + |
| + static final class FieldsTableModel extends TableModelBase<FieldsTableModel.Column> { |
| + |
| + enum Column implements TableColumnInfo { |
| + DEL("Del", 0, Boolean.class), |
| + NAME("Name", 1, String.class), |
| + TYPE("Type", 2, Class.class), |
| + OPTIONS("Options", 3, String.class), |
| + VALUE("Value", 4, String.class); |
| + |
| + private String colName; |
| + private int index; |
| + private Class<?> type; |
| + |
| + Column(String colName, int index, Class<?> type) { |
| + this.colName = colName; |
| + this.index = index; |
| + this.type = type; |
| + } |
| + |
| + @Override |
| + public String getColName() { |
| + return colName; |
| + } |
| + |
| + @Override |
| + public int getIndex() { |
| + return index; |
| + } |
| + |
| + @Override |
| + public Class<?> getType() { |
| + return type; |
| + } |
| + |
| + } |
| + |
| + private final List<NewField> newFieldList; |
| + |
| + FieldsTableModel(List<NewField> newFieldList) { |
| + super(newFieldList.size()); |
| + this.newFieldList = newFieldList; |
| + } |
| + |
| + @Override |
| + public Object getValueAt(int rowIndex, int columnIndex) { |
| + if (columnIndex == Column.OPTIONS.getIndex()) { |
| + return ""; |
| + } |
| + return data[rowIndex][columnIndex]; |
| + } |
| + |
| + @Override |
| + public boolean isCellEditable(int rowIndex, int columnIndex) { |
| + return columnIndex != Column.OPTIONS.getIndex(); |
| + } |
| + |
| + @SuppressWarnings("unchecked") |
| + @Override |
| + public void setValueAt(Object value, int rowIndex, int columnIndex) { |
| + data[rowIndex][columnIndex] = value; |
| + fireTableCellUpdated(rowIndex, columnIndex); |
| + NewField selectedField = newFieldList.get(rowIndex); |
| + if (columnIndex == Column.DEL.getIndex()) { |
| + selectedField.setDeleted((Boolean) value); |
| + } else if (columnIndex == Column.NAME.getIndex()) { |
| + selectedField.setName((String) value); |
| + } else if (columnIndex == Column.TYPE.getIndex()) { |
| + selectedField.setType((Class<? extends IndexableField>) value); |
| + selectedField.resetFieldType((Class<? extends IndexableField>) value); |
| + selectedField.setStored(selectedField.getFieldType().stored()); |
| + } else if (columnIndex == Column.VALUE.getIndex()) { |
| + selectedField.setValue((String) value); |
| + } |
| + } |
| + |
| + @Override |
| + protected Column[] columnInfos() { |
| + return Column.values(); |
| + } |
| + } |
| + |
| + static final class TypeCellRenderer implements TableCellRenderer { |
| + |
| + @SuppressWarnings("unchecked") |
| + @Override |
| + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { |
| + String simpleName = ((Class<? extends IndexableField>) value).getSimpleName(); |
| + return new JLabel(simpleName); |
| + } |
| + } |
| + |
| + static final class OptionsCellRenderer implements TableCellRenderer { |
| + |
| + private JDialog dialog; |
| + |
| + private final IndexOptionsDialogFactory indexOptionsDialogFactory; |
| + |
| + private final List<NewField> newFieldList; |
| + |
| + private final JPanel panel = new JPanel(); |
| + |
| + private JTable table; |
| + |
| + public OptionsCellRenderer(JDialog dialog, IndexOptionsDialogFactory indexOptionsDialogFactory, List<NewField> newFieldList) { |
| + this.dialog = dialog; |
| + this.indexOptionsDialogFactory = indexOptionsDialogFactory; |
| + this.newFieldList = newFieldList; |
| + } |
| + |
| + @Override |
| + @SuppressWarnings("unchecked") |
| + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { |
| + if (table != null && this.table != table) { |
| + this.table = table; |
| + final JTableHeader header = table.getTableHeader(); |
| + if (header != null) { |
| + panel.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0)); |
| + panel.setBorder(UIManager.getBorder("TableHeader.cellBorder")); |
| + panel.add(new JLabel(value.toString())); |
| + |
| + JLabel optionsLbl = new JLabel("options"); |
| + table.addMouseListener(new MouseAdapter() { |
| + @Override |
| + public void mouseClicked(MouseEvent e) { |
| + int row = table.rowAtPoint(e.getPoint()); |
| + int col = table.columnAtPoint(e.getPoint()); |
| + if (row >= 0 && col == FieldsTableModel.Column.OPTIONS.getIndex()) { |
| + String title = "Index options for:"; |
| + new DialogOpener<>(indexOptionsDialogFactory).open(dialog, title, 500, 500, |
| + (factory) -> { |
| + factory.setNewField(newFieldList.get(row)); |
| + }); |
| + } |
| + } |
| + }); |
| + panel.add(FontUtils.toLinkText(optionsLbl)); |
| + } |
| + } |
| + return panel; |
| + } |
| + |
| + } |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/AddDocumentDialogOperator.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/AddDocumentDialogOperator.java |
| new file mode 100644 |
| index 00000000000..2c29d6fd5db |
| --- /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 00000000000..7bea476a606 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/DocValuesDialogFactory.java |
| @@ -0,0 +1,296 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components.dialog.documents; |
| + |
| +import javax.swing.BorderFactory; |
| +import javax.swing.BoxLayout; |
| +import javax.swing.DefaultComboBoxModel; |
| +import javax.swing.DefaultListModel; |
| +import javax.swing.JButton; |
| +import javax.swing.JComboBox; |
| +import javax.swing.JDialog; |
| +import javax.swing.JLabel; |
| +import javax.swing.JList; |
| +import javax.swing.JPanel; |
| +import javax.swing.JScrollPane; |
| +import javax.swing.ListSelectionModel; |
| +import java.awt.BorderLayout; |
| +import java.awt.Dialog; |
| +import java.awt.Dimension; |
| +import java.awt.FlowLayout; |
| +import java.awt.Insets; |
| +import java.awt.Toolkit; |
| +import java.awt.Window; |
| +import java.awt.datatransfer.Clipboard; |
| +import java.awt.datatransfer.StringSelection; |
| +import java.awt.event.ActionEvent; |
| +import java.io.IOException; |
| +import java.util.ArrayList; |
| +import java.util.Arrays; |
| +import java.util.List; |
| +import java.util.Objects; |
| + |
| +import org.apache.lucene.luke.app.desktop.Preferences; |
| +import org.apache.lucene.luke.app.desktop.PreferencesFactory; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.FontUtils; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.models.documents.DocValues; |
| +import org.apache.lucene.luke.util.BytesRefUtils; |
| +import org.apache.lucene.util.NumericUtils; |
| + |
| +/** Factory of doc values dialog */ |
| +public final class DocValuesDialogFactory implements DialogOpener.DialogFactory { |
| + |
| + private static DocValuesDialogFactory instance; |
| + |
| + private final Preferences prefs; |
| + |
| + private final JComboBox<String> decodersCombo = new JComboBox<>(); |
| + |
| + private final JList<String> valueList = new JList<>(); |
| + |
| + private final ListenerFunctions listeners = new ListenerFunctions(); |
| + |
| + private JDialog dialog; |
| + |
| + private String field; |
| + |
| + private DocValues docValues; |
| + |
| + public synchronized static DocValuesDialogFactory getInstance() throws IOException { |
| + if (instance == null) { |
| + instance = new DocValuesDialogFactory(); |
| + } |
| + return instance; |
| + } |
| + |
| + private DocValuesDialogFactory() throws IOException { |
| + this.prefs = PreferencesFactory.getInstance(); |
| + } |
| + |
| + public void setValue(String field, DocValues docValues) { |
| + this.field = field; |
| + this.docValues = docValues; |
| + |
| + DefaultListModel<String> values = new DefaultListModel<>(); |
| + if (docValues.getValues().size() > 0) { |
| + decodersCombo.setEnabled(false); |
| + docValues.getValues().stream() |
| + .map(BytesRefUtils::decode) |
| + .forEach(values::addElement); |
| + } else if (docValues.getNumericValues().size() > 0) { |
| + decodersCombo.setEnabled(true); |
| + docValues.getNumericValues().stream() |
| + .map(String::valueOf) |
| + .forEach(values::addElement); |
| + } |
| + |
| + valueList.setModel(values); |
| + } |
| + |
| + @Override |
| + public JDialog create(Window owner, String title, int width, int height) { |
| + if (Objects.isNull(field) || Objects.isNull(docValues)) { |
| + throw new IllegalStateException("field name and/or doc values is not set."); |
| + } |
| + |
| + dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL); |
| + dialog.add(content()); |
| + dialog.setSize(new Dimension(width, height)); |
| + dialog.setLocationRelativeTo(owner); |
| + dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor()); |
| + return dialog; |
| + } |
| + |
| + private JPanel content() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); |
| + panel.add(headerPanel(), BorderLayout.PAGE_START); |
| + JScrollPane scrollPane = new JScrollPane(valueList()); |
| + scrollPane.setOpaque(false); |
| + scrollPane.getViewport().setOpaque(false); |
| + panel.add(scrollPane, BorderLayout.CENTER); |
| + panel.add(footerPanel(), BorderLayout.PAGE_END); |
| + return panel; |
| + } |
| + |
| + private JPanel headerPanel() { |
| + JPanel header = new JPanel(); |
| + header.setOpaque(false); |
| + header.setLayout(new BoxLayout(header, BoxLayout.PAGE_AXIS)); |
| + |
| + JPanel fieldHeader = new JPanel(new FlowLayout(FlowLayout.LEADING, 3, 3)); |
| + fieldHeader.setOpaque(false); |
| + fieldHeader.add(new JLabel(MessageUtils.getLocalizedMessage("documents.docvalues.label.doc_values"))); |
| + fieldHeader.add(new JLabel(field)); |
| + header.add(fieldHeader); |
| + |
| + JPanel typeHeader = new JPanel(new FlowLayout(FlowLayout.LEADING, 3, 3)); |
| + typeHeader.setOpaque(false); |
| + typeHeader.add(new JLabel(MessageUtils.getLocalizedMessage("documents.docvalues.label.type"))); |
| + typeHeader.add(new JLabel(docValues.getDvType().toString())); |
| + header.add(typeHeader); |
| + |
| + JPanel decodeHeader = new JPanel(new FlowLayout(FlowLayout.TRAILING, 3, 3)); |
| + decodeHeader.setOpaque(false); |
| + decodeHeader.add(new JLabel("decoded as")); |
| + String[] decoders = Arrays.stream(Decoder.values()).map(Decoder::toString).toArray(String[]::new); |
| + decodersCombo.setModel(new DefaultComboBoxModel<>(decoders)); |
| + decodersCombo.setSelectedItem(Decoder.LONG.toString()); |
| + decodersCombo.addActionListener(listeners::selectDecoder); |
| + decodeHeader.add(decodersCombo); |
| + if (docValues.getValues().size() > 0) { |
| + decodeHeader.setEnabled(false); |
| + } |
| + header.add(decodeHeader); |
| + |
| + return header; |
| + } |
| + |
| + private JList<String> valueList() { |
| + valueList.setVisibleRowCount(5); |
| + valueList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); |
| + valueList.setLayoutOrientation(JList.VERTICAL); |
| + |
| + DefaultListModel<String> values = new DefaultListModel<>(); |
| + if (docValues.getValues().size() > 0) { |
| + docValues.getValues().stream() |
| + .map(BytesRefUtils::decode) |
| + .forEach(values::addElement); |
| + } else { |
| + docValues.getNumericValues().stream() |
| + .map(String::valueOf) |
| + .forEach(values::addElement); |
| + } |
| + valueList.setModel(values); |
| + |
| + return valueList; |
| + } |
| + |
| + private JPanel footerPanel() { |
| + JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING, 5, 5)); |
| + footer.setOpaque(false); |
| + |
| + JButton copyBtn = new JButton(FontUtils.elegantIconHtml("", MessageUtils.getLocalizedMessage("button.copy"))); |
| + copyBtn.setMargin(new Insets(3, 0, 3, 0)); |
| + copyBtn.addActionListener(listeners::copyValues); |
| + footer.add(copyBtn); |
| + |
| + JButton closeBtn = new JButton(MessageUtils.getLocalizedMessage("button.close")); |
| + closeBtn.setMargin(new Insets(3, 0, 3, 0)); |
| + closeBtn.addActionListener(e -> dialog.dispose()); |
| + footer.add(closeBtn); |
| + |
| + return footer; |
| + } |
| + |
| + // control methods |
| + |
| + private void selectDecoder() { |
| + String decoderLabel = (String) decodersCombo.getSelectedItem(); |
| + Decoder decoder = Decoder.fromLabel(decoderLabel); |
| + |
| + if (docValues.getNumericValues().isEmpty()) { |
| + return; |
| + } |
| + |
| + DefaultListModel<String> values = new DefaultListModel<>(); |
| + switch (decoder) { |
| + case LONG: |
| + docValues.getNumericValues().stream() |
| + .map(String::valueOf) |
| + .forEach(values::addElement); |
| + break; |
| + case FLOAT: |
| + docValues.getNumericValues().stream() |
| + .mapToInt(Long::intValue) |
| + .mapToObj(NumericUtils::sortableIntToFloat) |
| + .map(String::valueOf) |
| + .forEach(values::addElement); |
| + break; |
| + case DOUBLE: |
| + docValues.getNumericValues().stream() |
| + .map(NumericUtils::sortableLongToDouble) |
| + .map(String::valueOf) |
| + .forEach(values::addElement); |
| + break; |
| + } |
| + |
| + valueList.setModel(values); |
| + } |
| + |
| + private void copyValues() { |
| + List<String> values = valueList.getSelectedValuesList(); |
| + if (values.isEmpty()) { |
| + values = getAllVlues(); |
| + } |
| + |
| + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); |
| + StringSelection selection = new StringSelection(String.join("\n", values)); |
| + clipboard.setContents(selection, null); |
| + } |
| + |
| + private List<String> getAllVlues() { |
| + List<String> values = new ArrayList<>(); |
| + for (int i = 0; i < valueList.getModel().getSize(); i++) { |
| + values.add(valueList.getModel().getElementAt(i)); |
| + } |
| + return values; |
| + } |
| + |
| + private class ListenerFunctions { |
| + |
| + void selectDecoder(ActionEvent e) { |
| + DocValuesDialogFactory.this.selectDecoder(); |
| + } |
| + |
| + void copyValues(ActionEvent e) { |
| + DocValuesDialogFactory.this.copyValues(); |
| + } |
| + } |
| + |
| + |
| + /** doc value decoders */ |
| + public enum Decoder { |
| + |
| + LONG("long"), FLOAT("float"), DOUBLE("double"); |
| + |
| + private final String label; |
| + |
| + Decoder(String label) { |
| + this.label = label; |
| + } |
| + |
| + @Override |
| + public String toString() { |
| + return label; |
| + } |
| + |
| + public static Decoder fromLabel(String label) { |
| + for (Decoder d : values()) { |
| + if (d.label.equalsIgnoreCase(label)) { |
| + return d; |
| + } |
| + } |
| + throw new IllegalArgumentException("No such decoder: " + label); |
| + } |
| + } |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/IndexOptionsDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/IndexOptionsDialogFactory.java |
| new file mode 100644 |
| index 00000000000..a0bda9cd973 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/IndexOptionsDialogFactory.java |
| @@ -0,0 +1,308 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components.dialog.documents; |
| + |
| +import javax.swing.BorderFactory; |
| +import javax.swing.BoxLayout; |
| +import javax.swing.JButton; |
| +import javax.swing.JCheckBox; |
| +import javax.swing.JComboBox; |
| +import javax.swing.JDialog; |
| +import javax.swing.JLabel; |
| +import javax.swing.JPanel; |
| +import javax.swing.JSeparator; |
| +import javax.swing.JTextField; |
| +import java.awt.Dialog; |
| +import java.awt.Dimension; |
| +import java.awt.FlowLayout; |
| +import java.awt.Insets; |
| +import java.awt.Window; |
| +import java.io.IOException; |
| +import java.util.Arrays; |
| + |
| +import org.apache.lucene.document.Field; |
| +import org.apache.lucene.document.FieldType; |
| +import org.apache.lucene.document.StringField; |
| +import org.apache.lucene.index.DocValuesType; |
| +import org.apache.lucene.index.IndexOptions; |
| +import org.apache.lucene.index.IndexableFieldType; |
| +import org.apache.lucene.luke.app.desktop.Preferences; |
| +import org.apache.lucene.luke.app.desktop.PreferencesFactory; |
| +import org.apache.lucene.luke.app.desktop.dto.documents.NewField; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| + |
| +/** Factory of index options dialog */ |
| +public final class IndexOptionsDialogFactory implements DialogOpener.DialogFactory { |
| + |
| + private static IndexOptionsDialogFactory instance; |
| + |
| + private final Preferences prefs; |
| + |
| + private final JCheckBox storedCB = new JCheckBox(); |
| + |
| + private final JCheckBox tokenizedCB = new JCheckBox(); |
| + |
| + private final JCheckBox omitNormsCB = new JCheckBox(); |
| + |
| + private final JComboBox<String> idxOptCombo = new JComboBox<>(availableIndexOptions()); |
| + |
| + private final JCheckBox storeTVCB = new JCheckBox(); |
| + |
| + private final JCheckBox storeTVPosCB = new JCheckBox(); |
| + |
| + private final JCheckBox storeTVOffCB = new JCheckBox(); |
| + |
| + private final JCheckBox storeTVPayCB = new JCheckBox(); |
| + |
| + private final JComboBox<String> dvTypeCombo = new JComboBox<>(availableDocValuesType()); |
| + |
| + private final JTextField dimCountTF = new JTextField(); |
| + |
| + private final JTextField dimNumBytesTF = new JTextField(); |
| + |
| + private JDialog dialog; |
| + |
| + private NewField nf; |
| + |
| + public synchronized static IndexOptionsDialogFactory getInstance() throws IOException { |
| + if (instance == null) { |
| + instance = new IndexOptionsDialogFactory(); |
| + } |
| + return instance; |
| + } |
| + |
| + private IndexOptionsDialogFactory() throws IOException { |
| + this.prefs = PreferencesFactory.getInstance(); |
| + initialize(); |
| + } |
| + |
| + private void initialize() { |
| + storedCB.setText(MessageUtils.getLocalizedMessage("idx_options.checkbox.stored")); |
| + storedCB.setOpaque(false); |
| + tokenizedCB.setText(MessageUtils.getLocalizedMessage("idx_options.checkbox.tokenized")); |
| + tokenizedCB.setOpaque(false); |
| + omitNormsCB.setText(MessageUtils.getLocalizedMessage("idx_options.checkbox.omit_norm")); |
| + omitNormsCB.setOpaque(false); |
| + idxOptCombo.setPreferredSize(new Dimension(300, idxOptCombo.getPreferredSize().height)); |
| + storeTVCB.setText(MessageUtils.getLocalizedMessage("idx_options.checkbox.store_tv")); |
| + storeTVCB.setOpaque(false); |
| + storeTVPosCB.setText(MessageUtils.getLocalizedMessage("idx_options.checkbox.store_tv_pos")); |
| + storeTVPosCB.setOpaque(false); |
| + storeTVOffCB.setText(MessageUtils.getLocalizedMessage("idx_options.checkbox.store_tv_off")); |
| + storeTVOffCB.setOpaque(false); |
| + storeTVPayCB.setText(MessageUtils.getLocalizedMessage("idx_options.checkbox.store_tv_pay")); |
| + storeTVPayCB.setOpaque(false); |
| + dimCountTF.setColumns(4); |
| + dimNumBytesTF.setColumns(4); |
| + } |
| + |
| + @Override |
| + public JDialog create(Window owner, String title, int width, int height) { |
| + dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL); |
| + dialog.add(content()); |
| + dialog.setSize(new Dimension(width, height)); |
| + dialog.setLocationRelativeTo(owner); |
| + dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor()); |
| + return dialog; |
| + } |
| + |
| + private JPanel content() { |
| + JPanel panel = new JPanel(); |
| + panel.setOpaque(false); |
| + panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); |
| + panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); |
| + |
| + panel.add(indexOptions()); |
| + panel.add(new JSeparator(JSeparator.HORIZONTAL)); |
| + panel.add(tvOptions()); |
| + panel.add(new JSeparator(JSeparator.HORIZONTAL)); |
| + panel.add(dvOptions()); |
| + panel.add(new JSeparator(JSeparator.HORIZONTAL)); |
| + panel.add(pvOptions()); |
| + panel.add(new JSeparator(JSeparator.HORIZONTAL)); |
| + panel.add(footer()); |
| + return panel; |
| + } |
| + |
| + private JPanel indexOptions() { |
| + JPanel panel = new JPanel(); |
| + panel.setOpaque(false); |
| + panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); |
| + |
| + JPanel inner1 = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 5)); |
| + inner1.setOpaque(false); |
| + inner1.add(storedCB); |
| + |
| + inner1.add(tokenizedCB); |
| + inner1.add(omitNormsCB); |
| + panel.add(inner1); |
| + |
| + JPanel inner2 = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 1)); |
| + inner2.setOpaque(false); |
| + JLabel idxOptLbl = new JLabel(MessageUtils.getLocalizedMessage("idx_options.label.index_options")); |
| + inner2.add(idxOptLbl); |
| + inner2.add(idxOptCombo); |
| + panel.add(inner2); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel tvOptions() { |
| + JPanel panel = new JPanel(); |
| + panel.setOpaque(false); |
| + panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); |
| + |
| + JPanel inner1 = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 2)); |
| + inner1.setOpaque(false); |
| + inner1.add(storeTVCB); |
| + panel.add(inner1); |
| + |
| + JPanel inner2 = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 2)); |
| + inner2.setOpaque(false); |
| + inner2.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0)); |
| + inner2.add(storeTVPosCB); |
| + inner2.add(storeTVOffCB); |
| + inner2.add(storeTVPayCB); |
| + panel.add(inner2); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel dvOptions() { |
| + JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 2)); |
| + panel.setOpaque(false); |
| + JLabel dvTypeLbl = new JLabel(MessageUtils.getLocalizedMessage("idx_options.label.dv_type")); |
| + panel.add(dvTypeLbl); |
| + panel.add(dvTypeCombo); |
| + return panel; |
| + } |
| + |
| + private JPanel pvOptions() { |
| + JPanel panel = new JPanel(); |
| + panel.setOpaque(false); |
| + panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS)); |
| + |
| + JPanel inner1 = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 2)); |
| + inner1.setOpaque(false); |
| + inner1.add(new JLabel(MessageUtils.getLocalizedMessage("idx_options.label.point_dims"))); |
| + panel.add(inner1); |
| + |
| + JPanel inner2 = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 2)); |
| + inner2.setOpaque(false); |
| + inner2.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0)); |
| + inner2.add(new JLabel(MessageUtils.getLocalizedMessage("idx_options.label.point_dc"))); |
| + inner2.add(dimCountTF); |
| + inner2.add(new JLabel(MessageUtils.getLocalizedMessage("idx_options.label.point_nb"))); |
| + inner2.add(dimNumBytesTF); |
| + panel.add(inner2); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel footer() { |
| + JPanel panel = new JPanel(new FlowLayout(FlowLayout.TRAILING)); |
| + panel.setOpaque(false); |
| + JButton okBtn = new JButton(MessageUtils.getLocalizedMessage("button.ok")); |
| + okBtn.setMargin(new Insets(3, 3, 3, 3)); |
| + okBtn.addActionListener(e -> saveOptions()); |
| + panel.add(okBtn); |
| + JButton cancelBtn = new JButton(MessageUtils.getLocalizedMessage("button.cancel")); |
| + cancelBtn.setMargin(new Insets(3, 3, 3, 3)); |
| + cancelBtn.addActionListener(e -> dialog.dispose()); |
| + panel.add(cancelBtn); |
| + |
| + return panel; |
| + } |
| + |
| + // control methods |
| + |
| + public void setNewField(NewField nf) { |
| + this.nf = nf; |
| + |
| + storedCB.setSelected(nf.isStored()); |
| + |
| + IndexableFieldType fieldType = nf.getFieldType(); |
| + tokenizedCB.setSelected(fieldType.tokenized()); |
| + omitNormsCB.setSelected(fieldType.omitNorms()); |
| + idxOptCombo.setSelectedItem(fieldType.indexOptions().name()); |
| + storeTVCB.setSelected(fieldType.storeTermVectors()); |
| + storeTVPosCB.setSelected(fieldType.storeTermVectorPositions()); |
| + storeTVOffCB.setSelected(fieldType.storeTermVectorOffsets()); |
| + storeTVPayCB.setSelected(fieldType.storeTermVectorPayloads()); |
| + dvTypeCombo.setSelectedItem(fieldType.docValuesType().name()); |
| + dimCountTF.setText(String.valueOf(fieldType.pointDataDimensionCount())); |
| + dimNumBytesTF.setText(String.valueOf(fieldType.pointNumBytes())); |
| + |
| + if (nf.getType().equals(org.apache.lucene.document.TextField.class) || |
| + nf.getType().equals(StringField.class) || |
| + nf.getType().equals(Field.class)) { |
| + storedCB.setEnabled(true); |
| + } else { |
| + storedCB.setEnabled(false); |
| + } |
| + |
| + if (nf.getType().equals(Field.class)) { |
| + tokenizedCB.setEnabled(true); |
| + omitNormsCB.setEnabled(true); |
| + idxOptCombo.setEnabled(true); |
| + storeTVCB.setEnabled(true); |
| + storeTVPosCB.setEnabled(true); |
| + storeTVOffCB.setEnabled(true); |
| + storeTVPosCB.setEnabled(true); |
| + } else { |
| + tokenizedCB.setEnabled(false); |
| + omitNormsCB.setEnabled(false); |
| + idxOptCombo.setEnabled(false); |
| + storeTVCB.setEnabled(false); |
| + storeTVPosCB.setEnabled(false); |
| + storeTVOffCB.setEnabled(false); |
| + storeTVPayCB.setEnabled(false); |
| + } |
| + |
| + // TODO |
| + dvTypeCombo.setEnabled(false); |
| + dimCountTF.setEnabled(false); |
| + dimNumBytesTF.setEnabled(false); |
| + } |
| + |
| + private void saveOptions() { |
| + nf.setStored(storedCB.isSelected()); |
| + if (nf.getType().equals(Field.class)) { |
| + FieldType ftype = (FieldType) nf.getFieldType(); |
| + ftype.setStored(storedCB.isSelected()); |
| + ftype.setTokenized(tokenizedCB.isSelected()); |
| + ftype.setOmitNorms(omitNormsCB.isSelected()); |
| + ftype.setIndexOptions(IndexOptions.valueOf((String) idxOptCombo.getSelectedItem())); |
| + ftype.setStoreTermVectors(storeTVCB.isSelected()); |
| + ftype.setStoreTermVectorPositions(storeTVPosCB.isSelected()); |
| + ftype.setStoreTermVectorOffsets(storeTVOffCB.isSelected()); |
| + ftype.setStoreTermVectorPayloads(storeTVPayCB.isSelected()); |
| + } |
| + dialog.dispose(); |
| + } |
| + |
| + private static String[] availableIndexOptions() { |
| + return Arrays.stream(IndexOptions.values()).map(IndexOptions::name).toArray(String[]::new); |
| + } |
| + |
| + private static String[] availableDocValuesType() { |
| + return Arrays.stream(DocValuesType.values()).map(DocValuesType::name).toArray(String[]::new); |
| + } |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/StoredValueDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/StoredValueDialogFactory.java |
| new file mode 100644 |
| index 00000000000..bd179f7e7ad |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/StoredValueDialogFactory.java |
| @@ -0,0 +1,132 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components.dialog.documents; |
| + |
| +import javax.swing.BorderFactory; |
| +import javax.swing.JButton; |
| +import javax.swing.JDialog; |
| +import javax.swing.JLabel; |
| +import javax.swing.JPanel; |
| +import javax.swing.JScrollPane; |
| +import javax.swing.JTextArea; |
| +import java.awt.BorderLayout; |
| +import java.awt.Color; |
| +import java.awt.Dialog; |
| +import java.awt.Dimension; |
| +import java.awt.FlowLayout; |
| +import java.awt.Insets; |
| +import java.awt.Toolkit; |
| +import java.awt.Window; |
| +import java.awt.datatransfer.Clipboard; |
| +import java.awt.datatransfer.StringSelection; |
| +import java.io.IOException; |
| +import java.util.Objects; |
| + |
| +import org.apache.lucene.luke.app.desktop.Preferences; |
| +import org.apache.lucene.luke.app.desktop.PreferencesFactory; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.FontUtils; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| + |
| +/** Factory of stored values dialog */ |
| +public final class StoredValueDialogFactory implements DialogOpener.DialogFactory { |
| + |
| + private static StoredValueDialogFactory instance; |
| + |
| + private final Preferences prefs; |
| + |
| + private JDialog dialog; |
| + |
| + private String field; |
| + |
| + private String value; |
| + |
| + public synchronized static StoredValueDialogFactory getInstance() throws IOException { |
| + if (instance == null) { |
| + instance = new StoredValueDialogFactory(); |
| + } |
| + return instance; |
| + } |
| + |
| + public void setField(String field) { |
| + this.field = field; |
| + } |
| + |
| + public void setValue(String value) { |
| + this.value = value; |
| + } |
| + |
| + private StoredValueDialogFactory() throws IOException { |
| + this.prefs = PreferencesFactory.getInstance(); |
| + } |
| + |
| + @Override |
| + public JDialog create(Window owner, String title, int width, int height) { |
| + if (Objects.isNull(field) || Objects.isNull(value)) { |
| + throw new IllegalStateException("field name and/or stored value is not set."); |
| + } |
| + |
| + dialog = new JDialog(owner, "Term Vector", Dialog.ModalityType.APPLICATION_MODAL); |
| + dialog.add(content()); |
| + dialog.setSize(new Dimension(width, height)); |
| + dialog.setLocationRelativeTo(owner); |
| + dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor()); |
| + return dialog; |
| + } |
| + |
| + private JPanel content() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); |
| + |
| + JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING, 5, 5)); |
| + header.setOpaque(false); |
| + header.add(new JLabel(MessageUtils.getLocalizedMessage("documents.stored.label.stored_value"))); |
| + header.add(new JLabel(field)); |
| + panel.add(header, BorderLayout.PAGE_START); |
| + |
| + JTextArea valueTA = new JTextArea(value); |
| + valueTA.setLineWrap(true); |
| + valueTA.setEditable(false); |
| + valueTA.setBackground(Color.white); |
| + JScrollPane scrollPane = new JScrollPane(valueTA); |
| + panel.add(scrollPane, BorderLayout.CENTER); |
| + |
| + JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING, 5, 5)); |
| + footer.setOpaque(false); |
| + |
| + JButton copyBtn = new JButton(FontUtils.elegantIconHtml("", MessageUtils.getLocalizedMessage("button.copy"))); |
| + copyBtn.setMargin(new Insets(3, 3, 3, 3)); |
| + copyBtn.addActionListener(e -> { |
| + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); |
| + StringSelection selection = new StringSelection(value); |
| + clipboard.setContents(selection, null); |
| + }); |
| + footer.add(copyBtn); |
| + |
| + JButton closeBtn = new JButton(MessageUtils.getLocalizedMessage("button.close")); |
| + closeBtn.setMargin(new Insets(3, 3, 3, 3)); |
| + closeBtn.addActionListener(e -> dialog.dispose()); |
| + footer.add(closeBtn); |
| + panel.add(footer, BorderLayout.PAGE_END); |
| + |
| + return panel; |
| + } |
| + |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/TermVectorDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/TermVectorDialogFactory.java |
| new file mode 100644 |
| index 00000000000..2e7da587af4 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/TermVectorDialogFactory.java |
| @@ -0,0 +1,189 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components.dialog.documents; |
| + |
| +import javax.swing.BorderFactory; |
| +import javax.swing.JButton; |
| +import javax.swing.JDialog; |
| +import javax.swing.JLabel; |
| +import javax.swing.JPanel; |
| +import javax.swing.JScrollPane; |
| +import javax.swing.JTable; |
| +import javax.swing.ListSelectionModel; |
| +import java.awt.BorderLayout; |
| +import java.awt.Dialog; |
| +import java.awt.Dimension; |
| +import java.awt.FlowLayout; |
| +import java.awt.Insets; |
| +import java.awt.Window; |
| +import java.io.IOException; |
| +import java.util.List; |
| +import java.util.Objects; |
| +import java.util.stream.Collectors; |
| + |
| +import org.apache.lucene.luke.app.desktop.Preferences; |
| +import org.apache.lucene.luke.app.desktop.PreferencesFactory; |
| +import org.apache.lucene.luke.app.desktop.components.TableColumnInfo; |
| +import org.apache.lucene.luke.app.desktop.components.TableModelBase; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.TableUtils; |
| +import org.apache.lucene.luke.models.documents.TermVectorEntry; |
| + |
| +/** Factory of term vector dialog */ |
| +public final class TermVectorDialogFactory implements DialogOpener.DialogFactory { |
| + |
| + private static TermVectorDialogFactory instance; |
| + |
| + private final Preferences prefs; |
| + |
| + private JDialog dialog; |
| + |
| + private String field; |
| + |
| + private List<TermVectorEntry> tvEntries; |
| + |
| + public synchronized static TermVectorDialogFactory getInstance() throws IOException { |
| + if (instance == null) { |
| + instance = new TermVectorDialogFactory(); |
| + } |
| + return instance; |
| + } |
| + |
| + private TermVectorDialogFactory() throws IOException { |
| + this.prefs = PreferencesFactory.getInstance(); |
| + } |
| + |
| + public void setField(String field) { |
| + this.field = field; |
| + } |
| + |
| + public void setTvEntries(List<TermVectorEntry> tvEntries) { |
| + this.tvEntries = tvEntries; |
| + } |
| + |
| + @Override |
| + public JDialog create(Window owner, String title, int width, int height) { |
| + if (Objects.isNull(field) || Objects.isNull(tvEntries)) { |
| + throw new IllegalStateException("field name and/or term vector is not set."); |
| + } |
| + |
| + dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL); |
| + dialog.add(content()); |
| + dialog.setSize(new Dimension(width, height)); |
| + dialog.setLocationRelativeTo(owner); |
| + dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor()); |
| + return dialog; |
| + } |
| + |
| + private JPanel content() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); |
| + |
| + JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING, 5, 5)); |
| + header.setOpaque(false); |
| + header.add(new JLabel(MessageUtils.getLocalizedMessage("documents.termvector.label.term_vector"))); |
| + header.add(new JLabel(field)); |
| + panel.add(header, BorderLayout.PAGE_START); |
| + |
| + JTable tvTable = new JTable(); |
| + TableUtils.setupTable(tvTable, ListSelectionModel.SINGLE_SELECTION, new TermVectorTableModel(tvEntries), null, 100, 50, 100); |
| + JScrollPane scrollPane = new JScrollPane(tvTable); |
| + panel.add(scrollPane, BorderLayout.CENTER); |
| + |
| + JPanel footer = new JPanel(new FlowLayout(FlowLayout.TRAILING, 0, 10)); |
| + footer.setOpaque(false); |
| + JButton closeBtn = new JButton(MessageUtils.getLocalizedMessage("button.close")); |
| + closeBtn.setMargin(new Insets(3, 3, 3, 3)); |
| + closeBtn.addActionListener(e -> dialog.dispose()); |
| + footer.add(closeBtn); |
| + panel.add(footer, BorderLayout.PAGE_END); |
| + |
| + return panel; |
| + } |
| + |
| + static final class TermVectorTableModel extends TableModelBase<TermVectorTableModel.Column> { |
| + |
| + enum Column implements TableColumnInfo { |
| + |
| + TERM("Term", 0, String.class), |
| + FREQ("Freq", 1, Long.class), |
| + POSITIONS("Positions", 2, String.class), |
| + OFFSETS("Offsets", 3, String.class); |
| + |
| + private String colName; |
| + private int index; |
| + private Class<?> type; |
| + |
| + Column(String colName, int index, Class<?> type) { |
| + this.colName = colName; |
| + this.index = index; |
| + this.type = type; |
| + } |
| + |
| + @Override |
| + public String getColName() { |
| + return colName; |
| + } |
| + |
| + @Override |
| + public int getIndex() { |
| + return index; |
| + } |
| + |
| + @Override |
| + public Class<?> getType() { |
| + return type; |
| + } |
| + } |
| + |
| + TermVectorTableModel() { |
| + super(); |
| + } |
| + |
| + TermVectorTableModel(List<TermVectorEntry> tvEntries) { |
| + super(tvEntries.size()); |
| + |
| + for (int i = 0; i < tvEntries.size(); i++) { |
| + TermVectorEntry entry = tvEntries.get(i); |
| + |
| + String termText = entry.getTermText(); |
| + long freq = tvEntries.get(i).getFreq(); |
| + String positions = String.join(",", |
| + entry.getPositions().stream() |
| + .map(pos -> Integer.toString(pos.getPosition())) |
| + .collect(Collectors.toList())); |
| + String offsets = String.join(",", |
| + entry.getPositions().stream() |
| + .filter(pos -> pos.getStartOffset().isPresent() && pos.getEndOffset().isPresent()) |
| + .map(pos -> Integer.toString(pos.getStartOffset().orElse(-1)) + "-" + Integer.toString(pos.getEndOffset().orElse(-1))) |
| + .collect(Collectors.toList()) |
| + ); |
| + |
| + data[i] = new Object[]{termText, freq, positions, offsets}; |
| + } |
| + |
| + } |
| + |
| + @Override |
| + protected Column[] columnInfos() { |
| + return Column.values(); |
| + } |
| + } |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/package-info.java |
| new file mode 100644 |
| index 00000000000..9c641f99469 |
| --- /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 00000000000..e9d9c9731a6 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/AboutDialogFactory.java |
| @@ -0,0 +1,200 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components.dialog.menubar; |
| + |
| +import javax.swing.BorderFactory; |
| +import javax.swing.BoxLayout; |
| +import javax.swing.JButton; |
| +import javax.swing.JDialog; |
| +import javax.swing.JEditorPane; |
| +import javax.swing.JLabel; |
| +import javax.swing.JPanel; |
| +import javax.swing.JScrollPane; |
| +import javax.swing.ScrollPaneConstants; |
| +import javax.swing.SwingUtilities; |
| +import javax.swing.event.HyperlinkEvent; |
| +import javax.swing.event.HyperlinkListener; |
| +import java.awt.BorderLayout; |
| +import java.awt.Color; |
| +import java.awt.Desktop; |
| +import java.awt.Dialog; |
| +import java.awt.Dimension; |
| +import java.awt.FlowLayout; |
| +import java.awt.Font; |
| +import java.awt.GridLayout; |
| +import java.awt.Insets; |
| +import java.awt.Window; |
| +import java.io.IOException; |
| +import java.net.URISyntaxException; |
| +import java.util.Objects; |
| + |
| +import org.apache.lucene.LucenePackage; |
| +import org.apache.lucene.luke.app.desktop.Preferences; |
| +import org.apache.lucene.luke.app.desktop.PreferencesFactory; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.FontUtils; |
| +import org.apache.lucene.luke.app.desktop.util.ImageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.URLLabel; |
| +import org.apache.lucene.luke.models.LukeException; |
| + |
| +/** Factory of about dialog */ |
| +public final class AboutDialogFactory implements DialogOpener.DialogFactory { |
| + |
| + private static AboutDialogFactory instance; |
| + |
| + private final Preferences prefs; |
| + |
| + private JDialog dialog; |
| + |
| + public synchronized static AboutDialogFactory getInstance() throws IOException { |
| + if (instance == null) { |
| + instance = new AboutDialogFactory(); |
| + } |
| + return instance; |
| + } |
| + |
| + private AboutDialogFactory() throws IOException { |
| + this.prefs = PreferencesFactory.getInstance(); |
| + } |
| + |
| + @Override |
| + public JDialog create(Window owner, String title, int width, int height) { |
| + dialog = new JDialog(owner, title, Dialog.ModalityType.APPLICATION_MODAL); |
| + dialog.add(content()); |
| + dialog.setSize(new Dimension(width, height)); |
| + dialog.setLocationRelativeTo(owner); |
| + dialog.getContentPane().setBackground(prefs.getColorTheme().getBackgroundColor()); |
| + return dialog; |
| + } |
| + |
| + private JPanel content() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); |
| + |
| + panel.add(header(), BorderLayout.PAGE_START); |
| + panel.add(center(), BorderLayout.CENTER); |
| + panel.add(footer(), BorderLayout.PAGE_END); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel header() { |
| + JPanel panel = new JPanel(new GridLayout(3, 1)); |
| + panel.setOpaque(false); |
| + |
| + JPanel logo = new JPanel(new FlowLayout(FlowLayout.CENTER)); |
| + logo.setOpaque(false); |
| + logo.add(new JLabel(ImageUtils.createImageIcon("luke-logo.gif", 200, 40))); |
| + panel.add(logo); |
| + |
| + JPanel project = new JPanel(new FlowLayout(FlowLayout.CENTER)); |
| + project.setOpaque(false); |
| + JLabel projectLbl = new JLabel("Lucene Toolbox Project"); |
| + projectLbl.setFont(new Font(projectLbl.getFont().getFontName(), Font.BOLD, 32)); |
| + projectLbl.setForeground(Color.decode("#5aaa88")); |
| + project.add(projectLbl); |
| + panel.add(project); |
| + |
| + JPanel desc = new JPanel(); |
| + desc.setOpaque(false); |
| + desc.setLayout(new BoxLayout(desc, BoxLayout.PAGE_AXIS)); |
| + |
| + JPanel subTitle = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 5)); |
| + subTitle.setOpaque(false); |
| + JLabel subTitleLbl = new JLabel("GUI client of the best Java search library Apache Lucene"); |
| + subTitleLbl.setFont(new Font(subTitleLbl.getFont().getFontName(), Font.PLAIN, 20)); |
| + subTitle.add(subTitleLbl); |
| + subTitle.add(new JLabel(ImageUtils.createImageIcon("lucene-logo.gif", 100, 15))); |
| + desc.add(subTitle); |
| + |
| + JPanel link = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 5)); |
| + link.setOpaque(false); |
| + JLabel linkLbl = FontUtils.toLinkText(new URLLabel("https://lucene.apache.org/")); |
| + link.add(linkLbl); |
| + desc.add(link); |
| + |
| + panel.add(desc); |
| + |
| + return panel; |
| + } |
| + |
| + private JScrollPane center() { |
| + JEditorPane editorPane = new JEditorPane(); |
| + editorPane.setOpaque(false); |
| + editorPane.setMargin(new Insets(0, 5, 2, 5)); |
| + editorPane.setContentType("text/html"); |
| + editorPane.setText(LICENSE_NOTICE); |
| + editorPane.setEditable(false); |
| + editorPane.addHyperlinkListener(hyperlinkListener); |
| + JScrollPane scrollPane = new JScrollPane(editorPane, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); |
| + scrollPane.setBorder(BorderFactory.createLineBorder(Color.gray)); |
| + SwingUtilities.invokeLater(() -> { |
| + // Set the scroll bar position to top |
| + scrollPane.getVerticalScrollBar().setValue(0); |
| + }); |
| + return scrollPane; |
| + } |
| + |
| + private JPanel footer() { |
| + JPanel panel = new JPanel(new FlowLayout(FlowLayout.TRAILING)); |
| + panel.setOpaque(false); |
| + JButton closeBtn = new JButton(MessageUtils.getLocalizedMessage("button.close")); |
| + closeBtn.setMargin(new Insets(5, 5, 5, 5)); |
| + if (closeBtn.getActionListeners().length == 0) { |
| + closeBtn.addActionListener(e -> dialog.dispose()); |
| + } |
| + panel.add(closeBtn); |
| + return panel; |
| + } |
| + |
| + private static final String LUCENE_IMPLEMENTATION_VERSION = LucenePackage.get().getImplementationVersion(); |
| + |
| + private static final String LICENSE_NOTICE = |
| + "<p>[Implementation Version]</p>" + |
| + "<p>" + (Objects.nonNull(LUCENE_IMPLEMENTATION_VERSION) ? LUCENE_IMPLEMENTATION_VERSION : "") + "</p>" + |
| + "<p>[License]</p>" + |
| + "<p>Luke is distributed under <a href=\"http://www.apache.org/licenses/LICENSE-2.0\">Apache License Version 2.0</a> (http://www.apache.org/licenses/LICENSE-2.0) " + |
| + "and includes <a href=\"https://www.elegantthemes.com/blog/resources/elegant-icon-font\">The Elegant Icon Font</a> (https://www.elegantthemes.com/blog/resources/elegant-icon-font) " + |
| + "licensed under <a href=\"https://opensource.org/licenses/MIT\">MIT</a> (https://opensource.org/licenses/MIT)</p>" + |
| + "<p>[Brief history]</p>" + |
| + "<ul>" + |
| + "<li>The original author is Andrzej Bialecki</li>" + |
| + "<li>The project has been mavenized by Neil Ireson</li>" + |
| + "<li>The project has been ported to Lucene trunk (marked as 5.0 at the time) by Dmitry Kan\n</li>" + |
| + "<li>The project has been back-ported to Lucene 4.3 by sonarname</li>" + |
| + "<li>There are updates to the (non-mavenized) project done by tarzanek</li>" + |
| + "<li>The UI and core components has been re-implemented on top of Swing by Tomoko Uchida</li>" + |
| + "</ul>" |
| + ; |
| + |
| + |
| + private static final HyperlinkListener hyperlinkListener = e -> { |
| + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) |
| + if (Desktop.isDesktopSupported()) { |
| + try { |
| + Desktop.getDesktop().browse(e.getURL().toURI()); |
| + } catch (IOException | URISyntaxException ex) { |
| + throw new LukeException(ex.getMessage(), ex); |
| + } |
| + } |
| + }; |
| + |
| + |
| +} |
| 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 00000000000..3928ba699b3 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/CheckIndexDialogFactory.java |
| @@ -0,0 +1,387 @@ |
| +/* |
| + * 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.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.IOException; |
| +import java.io.UnsupportedEncodingException; |
| +import java.lang.invoke.MethodHandles; |
| +import java.util.concurrent.ExecutorService; |
| +import java.util.concurrent.Executors; |
| + |
| +import org.apache.logging.log4j.Logger; |
| +import org.apache.lucene.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.PreferencesFactory; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.FontUtils; |
| +import org.apache.lucene.luke.app.desktop.util.ImageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.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.luke.util.LoggerFactory; |
| +import org.apache.lucene.util.NamedThreadFactory; |
| + |
| +/** Factory of check index dialog */ |
| +public final class CheckIndexDialogFactory implements DialogOpener.DialogFactory { |
| + |
| + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| + |
| + private static CheckIndexDialogFactory instance; |
| + |
| + private final Preferences prefs; |
| + |
| + private final IndexToolsFactory indexToolsFactory; |
| + |
| + private final DirectoryHandler directoryHandler; |
| + |
| + 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(); |
| + |
| + public synchronized static CheckIndexDialogFactory getInstance() throws IOException { |
| + if (instance == null) { |
| + instance = new CheckIndexDialogFactory(); |
| + } |
| + return instance; |
| + } |
| + |
| + private CheckIndexDialogFactory() throws IOException { |
| + this.prefs = PreferencesFactory.getInstance(); |
| + this.indexToolsFactory = new IndexToolsFactory(); |
| + this.indexHandler = IndexHandler.getInstance(); |
| + this.directoryHandler = DirectoryHandler.getInstance(); |
| + |
| + 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("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<CheckIndex.Status, Void> task = new SwingWorker<CheckIndex.Status, Void>() { |
| + |
| + @Override |
| + protected CheckIndex.Status doInBackground() { |
| + setProgress(0); |
| + statusLbl.setText("Running..."); |
| + indicatorLbl.setVisible(true); |
| + TextAreaPrintStream ps; |
| + try { |
| + ps = new TextAreaPrintStream(logArea); |
| + CheckIndex.Status status = toolsModel.checkIndex(ps); |
| + ps.flush(); |
| + return status; |
| + } catch (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(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<CheckIndex.Status, Void> task = new SwingWorker<CheckIndex.Status, Void>() { |
| + |
| + @Override |
| + protected CheckIndex.Status doInBackground() { |
| + setProgress(0); |
| + statusLbl.setText("Running..."); |
| + indicatorLbl.setVisible(true); |
| + logArea.setText(""); |
| + TextAreaPrintStream ps; |
| + try { |
| + ps = new TextAreaPrintStream(logArea); |
| + toolsModel.repairIndex(status, ps); |
| + statusLbl.setText("Done"); |
| + ps.flush(); |
| + return status; |
| + } catch (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/CreateIndexDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/CreateIndexDialogFactory.java |
| new file mode 100644 |
| index 00000000000..03c6262af7c |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/CreateIndexDialogFactory.java |
| @@ -0,0 +1,356 @@ |
| +/* |
| + * 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.JFileChooser; |
| +import javax.swing.JLabel; |
| +import javax.swing.JOptionPane; |
| +import javax.swing.JPanel; |
| +import javax.swing.JSeparator; |
| +import javax.swing.JTextArea; |
| +import javax.swing.JTextField; |
| +import javax.swing.SwingWorker; |
| +import java.awt.BorderLayout; |
| +import java.awt.Dialog; |
| +import java.awt.Dimension; |
| +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.nio.file.FileVisitResult; |
| +import java.nio.file.Files; |
| +import java.nio.file.Path; |
| +import java.nio.file.Paths; |
| +import java.nio.file.SimpleFileVisitor; |
| +import java.nio.file.attribute.BasicFileAttributes; |
| +import java.util.concurrent.ExecutorService; |
| +import java.util.concurrent.Executors; |
| + |
| +import org.apache.logging.log4j.Logger; |
| +import org.apache.lucene.luke.app.IndexHandler; |
| +import org.apache.lucene.luke.app.desktop.Preferences; |
| +import org.apache.lucene.luke.app.desktop.PreferencesFactory; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.FontUtils; |
| +import org.apache.lucene.luke.app.desktop.util.ImageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.StyleConstants; |
| +import org.apache.lucene.luke.app.desktop.util.URLLabel; |
| +import org.apache.lucene.luke.models.tools.IndexTools; |
| +import org.apache.lucene.luke.models.tools.IndexToolsFactory; |
| +import org.apache.lucene.luke.util.LoggerFactory; |
| +import org.apache.lucene.store.Directory; |
| +import org.apache.lucene.store.FSDirectory; |
| +import org.apache.lucene.util.NamedThreadFactory; |
| +import org.apache.lucene.util.SuppressForbidden; |
| + |
| +/** Factory of create index dialog */ |
| +public class CreateIndexDialogFactory implements DialogOpener.DialogFactory { |
| + |
| + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| + |
| + private static CreateIndexDialogFactory instance; |
| + |
| + private final Preferences prefs; |
| + |
| + private final IndexHandler indexHandler; |
| + |
| + private final JTextField locationTF = new JTextField(); |
| + |
| + private final JButton browseBtn = new JButton(); |
| + |
| + private final JTextField dirnameTF = new JTextField(); |
| + |
| + private final JTextField dataDirTF = new JTextField(); |
| + |
| + private final JButton dataBrowseBtn = new JButton(); |
| + |
| + private final JButton clearBtn = new JButton(); |
| + |
| + private final JLabel indicatorLbl = new JLabel(); |
| + |
| + private final JButton createBtn = new JButton(); |
| + |
| + private final JButton cancelBtn = new JButton(); |
| + |
| + private final ListenerFunctions listeners = new ListenerFunctions(); |
| + |
| + private JDialog dialog; |
| + |
| + public synchronized static CreateIndexDialogFactory getInstance() throws IOException { |
| + if (instance == null) { |
| + instance = new CreateIndexDialogFactory(); |
| + } |
| + return instance; |
| + } |
| + |
| + private CreateIndexDialogFactory() throws IOException { |
| + this.prefs = PreferencesFactory.getInstance(); |
| + this.indexHandler = IndexHandler.getInstance(); |
| + initialize(); |
| + } |
| + |
| + private void initialize() { |
| + locationTF.setPreferredSize(new Dimension(360, 30)); |
| + locationTF.setText(System.getProperty("user.home")); |
| + locationTF.setEditable(false); |
| + |
| + browseBtn.setText(FontUtils.elegantIconHtml("n", MessageUtils.getLocalizedMessage("button.browse"))); |
| + browseBtn.setFont(StyleConstants.FONT_BUTTON_LARGE); |
| + browseBtn.setPreferredSize(new Dimension(120, 30)); |
| + browseBtn.addActionListener(listeners::browseLocationDirectory); |
| + |
| + dirnameTF.setPreferredSize(new Dimension(200, 30)); |
| + |
| + dataDirTF.setPreferredSize(new Dimension(250, 30)); |
| + dataDirTF.setEditable(false); |
| + |
| + clearBtn.setText(MessageUtils.getLocalizedMessage("button.clear")); |
| + clearBtn.setPreferredSize(new Dimension(70, 30)); |
| + clearBtn.addActionListener(listeners::clearDataDir); |
| + |
| + dataBrowseBtn.setText(FontUtils.elegantIconHtml("n", MessageUtils.getLocalizedMessage("button.browse"))); |
| + dataBrowseBtn.setFont(StyleConstants.FONT_BUTTON_LARGE); |
| + dataBrowseBtn.setPreferredSize(new Dimension(100, 30)); |
| + dataBrowseBtn.addActionListener(listeners::browseDataDirectory); |
| + |
| + indicatorLbl.setIcon(ImageUtils.createImageIcon("indicator.gif", 20, 20)); |
| + indicatorLbl.setVisible(false); |
| + |
| + createBtn.setText(MessageUtils.getLocalizedMessage("button.create")); |
| + createBtn.addActionListener(listeners::createIndex); |
| + |
| + cancelBtn.setText(MessageUtils.getLocalizedMessage("button.cancel")); |
| + cancelBtn.addActionListener(e -> dialog.dispose()); |
| + } |
| + |
| + @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(optionalSettings()); |
| + 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 locPath = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + locPath.setOpaque(false); |
| + locPath.add(new JLabel(MessageUtils.getLocalizedMessage("createindex.label.location"))); |
| + locPath.add(locationTF); |
| + locPath.add(browseBtn); |
| + panel.add(locPath); |
| + |
| + JPanel dirName = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + dirName.setOpaque(false); |
| + dirName.add(new JLabel(MessageUtils.getLocalizedMessage("createindex.label.dirname"))); |
| + dirName.add(dirnameTF); |
| + panel.add(dirName); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel optionalSettings() { |
| + JPanel panel = new JPanel(new BorderLayout()); |
| + panel.setOpaque(false); |
| + |
| + JPanel description = new JPanel(); |
| + description.setLayout(new BoxLayout(description, BoxLayout.Y_AXIS)); |
| + description.setOpaque(false); |
| + |
| + JPanel name = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + name.setOpaque(false); |
| + JLabel nameLbl = new JLabel(MessageUtils.getLocalizedMessage("createindex.label.option")); |
| + name.add(nameLbl); |
| + description.add(name); |
| + |
| + JTextArea descTA1 = new JTextArea(MessageUtils.getLocalizedMessage("createindex.textarea.data_help1")); |
| + descTA1.setPreferredSize(new Dimension(550, 20)); |
| + descTA1.setBorder(BorderFactory.createEmptyBorder(2, 10, 10, 5)); |
| + descTA1.setOpaque(false); |
| + descTA1.setLineWrap(true); |
| + descTA1.setEditable(false); |
| + description.add(descTA1); |
| + |
| + JPanel link = new JPanel(new FlowLayout(FlowLayout.LEADING, 10, 1)); |
| + link.setOpaque(false); |
| + JLabel linkLbl = FontUtils.toLinkText(new URLLabel(MessageUtils.getLocalizedMessage("createindex.label.data_link"))); |
| + link.add(linkLbl); |
| + description.add(link); |
| + |
| + JTextArea descTA2 = new JTextArea(MessageUtils.getLocalizedMessage("createindex.textarea.data_help2")); |
| + descTA2.setPreferredSize(new Dimension(550, 50)); |
| + descTA2.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 5)); |
| + descTA2.setOpaque(false); |
| + descTA2.setLineWrap(true); |
| + descTA2.setEditable(false); |
| + description.add(descTA2); |
| + |
| + panel.add(description, BorderLayout.PAGE_START); |
| + |
| + JPanel dataDirPath = new JPanel(new FlowLayout(FlowLayout.LEADING)); |
| + dataDirPath.setOpaque(false); |
| + dataDirPath.add(new JLabel(MessageUtils.getLocalizedMessage("createindex.label.datadir"))); |
| + dataDirPath.add(dataDirTF); |
| + dataDirPath.add(dataBrowseBtn); |
| + |
| + dataDirPath.add(clearBtn); |
| + panel.add(dataDirPath, BorderLayout.CENTER); |
| + |
| + return panel; |
| + } |
| + |
| + private JPanel buttons() { |
| + JPanel panel = new JPanel(new FlowLayout(FlowLayout.TRAILING)); |
| + panel.setOpaque(false); |
| + panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 10, 20)); |
| + |
| + panel.add(indicatorLbl); |
| + panel.add(createBtn); |
| + panel.add(cancelBtn); |
| + |
| + return panel; |
| + } |
| + |
| + private class ListenerFunctions { |
| + |
| + void browseLocationDirectory(ActionEvent e) { |
| + browseDirectory(locationTF); |
| + } |
| + |
| + void browseDataDirectory(ActionEvent e) { |
| + browseDirectory(dataDirTF); |
| + } |
| + |
| + @SuppressForbidden(reason = "JFilechooser#getSelectedFile() returns java.io.File") |
| + private void browseDirectory(JTextField tf) { |
| + JFileChooser fc = new JFileChooser(new File(tf.getText())); |
| + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); |
| + fc.setFileHidingEnabled(false); |
| + int retVal = fc.showOpenDialog(dialog); |
| + if (retVal == JFileChooser.APPROVE_OPTION) { |
| + File dir = fc.getSelectedFile(); |
| + tf.setText(dir.getAbsolutePath()); |
| + } |
| + } |
| + |
| + void createIndex(ActionEvent e) { |
| + Path path = Paths.get(locationTF.getText(), dirnameTF.getText()); |
| + if (Files.exists(path)) { |
| + String message = "The directory " + path.toAbsolutePath().toString() + " already exists."; |
| + JOptionPane.showMessageDialog(dialog, message, "Empty index path", JOptionPane.ERROR_MESSAGE); |
| + } else { |
| + // create new index asynchronously |
| + ExecutorService executor = Executors.newFixedThreadPool(1, new NamedThreadFactory("create-index-dialog")); |
| + |
| + SwingWorker<Void, Void> task = new SwingWorker<Void, Void>() { |
| + |
| + @Override |
| + protected Void doInBackground() throws Exception { |
| + setProgress(0); |
| + indicatorLbl.setVisible(true); |
| + createBtn.setEnabled(false); |
| + |
| + try { |
| + Directory dir = FSDirectory.open(path); |
| + IndexTools toolsModel = new IndexToolsFactory().newInstance(dir); |
| + |
| + if (dataDirTF.getText().isEmpty()) { |
| + // without sample documents |
| + toolsModel.createNewIndex(); |
| + } else { |
| + // with sample documents |
| + Path dataPath = Paths.get(dataDirTF.getText()); |
| + toolsModel.createNewIndex(dataPath.toAbsolutePath().toString()); |
| + } |
| + |
| + indexHandler.open(path.toAbsolutePath().toString(), null, false, false, false); |
| + prefs.addHistory(path.toAbsolutePath().toString()); |
| + |
| + dirnameTF.setText(""); |
| + closeDialog(); |
| + } catch (Exception ex) { |
| + // cleanup |
| + try { |
| + Files.walkFileTree(path, new SimpleFileVisitor<Path>() { |
| + @Override |
| + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { |
| + Files.delete(file); |
| + return FileVisitResult.CONTINUE; |
| + } |
| + }); |
| + Files.deleteIfExists(path); |
| + } catch (IOException ex2) { |
| + } |
| + |
| + log.error("Cannot create index", ex); |
| + String message = "See Logs tab or log file for more details."; |
| + JOptionPane.showMessageDialog(dialog, message, "Cannot create index", JOptionPane.ERROR_MESSAGE); |
| + } finally { |
| + setProgress(100); |
| + } |
| + return null; |
| + } |
| + |
| + @Override |
| + protected void done() { |
| + indicatorLbl.setVisible(false); |
| + createBtn.setEnabled(true); |
| + } |
| + }; |
| + |
| + executor.submit(task); |
| + executor.shutdown(); |
| + } |
| + } |
| + |
| + private void clearDataDir(ActionEvent e) { |
| + dataDirTF.setText(""); |
| + } |
| + |
| + private void closeDialog() { |
| + dialog.dispose(); |
| + } |
| + } |
| +} |
| 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 00000000000..782827d9744 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OpenIndexDialogFactory.java |
| @@ -0,0 +1,385 @@ |
| +/* |
| + * 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.nio.file.Files; |
| +import java.nio.file.Path; |
| +import java.nio.file.Paths; |
| +import java.util.ArrayList; |
| +import java.util.List; |
| +import java.util.Set; |
| +import java.util.concurrent.ExecutorService; |
| +import java.util.concurrent.Executors; |
| +import java.util.stream.Collectors; |
| + |
| +import org.apache.logging.log4j.Logger; |
| +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.PreferencesFactory; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.FontUtils; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.StyleConstants; |
| +import org.apache.lucene.luke.models.LukeException; |
| +import org.apache.lucene.luke.util.LoggerFactory; |
| +import org.apache.lucene.luke.util.reflection.ClassScanner; |
| +import org.apache.lucene.store.FSDirectory; |
| +import org.apache.lucene.util.NamedThreadFactory; |
| +import org.apache.lucene.util.SuppressForbidden; |
| + |
| +/** Factory of open index dialog */ |
| +public final class OpenIndexDialogFactory implements DialogOpener.DialogFactory { |
| + |
| + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| + |
| + private static OpenIndexDialogFactory instance; |
| + |
| + private final Preferences prefs; |
| + |
| + private final DirectoryHandler directoryHandler; |
| + |
| + private final IndexHandler indexHandler; |
| + |
| + private final JComboBox<String> idxPathCombo = new JComboBox<>(); |
| + |
| + private final JButton browseBtn = new JButton(); |
| + |
| + private final JCheckBox readOnlyCB = new JCheckBox(); |
| + |
| + private final JComboBox<String> 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; |
| + |
| + public synchronized static OpenIndexDialogFactory getInstance() throws IOException { |
| + if (instance == null) { |
| + instance = new OpenIndexDialogFactory(); |
| + } |
| + return instance; |
| + } |
| + |
| + private OpenIndexDialogFactory() throws IOException { |
| + this.prefs = PreferencesFactory.getInstance(); |
| + this.directoryHandler = DirectoryHandler.getInstance(); |
| + this.indexHandler = IndexHandler.getInstance(); |
| + 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.addActionListener(listeners::toggleReadOnly); |
| + readOnlyCB.setOpaque(false); |
| + |
| + // Scanning all Directory types will take time... |
| + ExecutorService executorService = Executors.newFixedThreadPool(1, new NamedThreadFactory("load-directory-types")); |
| + executorService.execute(() -> { |
| + for (String clazzName : supportedDirImpls()) { |
| + dirImplCombo.addItem(clazzName); |
| + } |
| + }); |
| + executorService.shutdown(); |
| + 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 |
| + ClassScanner scanner = new ClassScanner("org.apache.lucene.store", getClass().getClassLoader()); |
| + Set<Class<? extends FSDirectory>> clazzSet = scanner.scanSubTypes(FSDirectory.class); |
| + |
| + List<String> clazzNames = new ArrayList<>(); |
| + clazzNames.add(FSDirectory.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) { |
| + File currentDir = getLastOpenedDirectory(); |
| + JFileChooser fc = currentDir == null ? new JFileChooser() : new JFileChooser(currentDir); |
| + 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); |
| + } |
| + } |
| + |
| + @SuppressForbidden(reason = "JFileChooser constructor takes java.io.File") |
| + private File getLastOpenedDirectory() { |
| + List<String> history = prefs.getHistory(); |
| + if (!history.isEmpty()) { |
| + Path path = Paths.get(history.get(0)); |
| + if (Files.exists(path)) { |
| + return path.getParent().toAbsolutePath().toFile(); |
| + } |
| + } |
| + return null; |
| + } |
| + |
| + 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 message = MessageUtils.getLocalizedMessage("openindex.message.index_path_not_selected"); |
| + JOptionPane.showMessageDialog(dialog, message, "Empty index path", JOptionPane.ERROR_MESSAGE); |
| + } else if (isNoReader()) { |
| + directoryHandler.open(selectedPath, dirImplClazz); |
| + addHistory(selectedPath); |
| + } else { |
| + indexHandler.open(selectedPath, dirImplClazz, isReadOnly(), useCompound(), keepAllCommits()); |
| + 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 00000000000..e5543d86856 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OptimizeIndexDialogFactory.java |
| @@ -0,0 +1,263 @@ |
| +/* |
| + * 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.IOException; |
| +import java.io.UnsupportedEncodingException; |
| +import java.lang.invoke.MethodHandles; |
| +import java.util.concurrent.ExecutorService; |
| +import java.util.concurrent.Executors; |
| + |
| +import org.apache.logging.log4j.Logger; |
| +import org.apache.lucene.luke.app.IndexHandler; |
| +import org.apache.lucene.luke.app.IndexObserver; |
| +import org.apache.lucene.luke.app.LukeState; |
| +import org.apache.lucene.luke.app.desktop.Preferences; |
| +import org.apache.lucene.luke.app.desktop.PreferencesFactory; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.FontUtils; |
| +import org.apache.lucene.luke.app.desktop.util.ImageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.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.luke.util.LoggerFactory; |
| +import org.apache.lucene.util.NamedThreadFactory; |
| + |
| +/** Factory of optimize index dialog */ |
| +public final class OptimizeIndexDialogFactory implements DialogOpener.DialogFactory { |
| + |
| + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| + |
| + private static OptimizeIndexDialogFactory instance; |
| + |
| + private final Preferences prefs; |
| + |
| + private final IndexToolsFactory indexToolsFactory = new 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; |
| + |
| + public synchronized static OptimizeIndexDialogFactory getInstance() throws IOException { |
| + if (instance == null) { |
| + instance = new OptimizeIndexDialogFactory(); |
| + } |
| + return instance; |
| + } |
| + |
| + private OptimizeIndexDialogFactory() throws IOException { |
| + this.prefs = PreferencesFactory.getInstance(); |
| + this.indexHandler = IndexHandler.getInstance(); |
| + 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("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<Void, Void> task = new SwingWorker<Void, Void>() { |
| + |
| + @Override |
| + protected Void doInBackground() { |
| + setProgress(0); |
| + statusLbl.setText("Running..."); |
| + indicatorLbl.setVisible(true); |
| + TextAreaPrintStream ps; |
| + try { |
| + ps = new TextAreaPrintStream(logArea); |
| + toolsModel.optimize(expungeCB.isSelected(), (int) maxSegSpnr.getValue(), ps); |
| + ps.flush(); |
| + } catch (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 00000000000..72a2d3fc7d5 |
| --- /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 00000000000..44ad40b04fd |
| --- /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 00000000000..66d558d2866 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/search/ExplainDialogFactory.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.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.io.IOException; |
| +import java.util.Objects; |
| +import java.util.stream.IntStream; |
| + |
| +import org.apache.lucene.luke.app.desktop.Preferences; |
| +import org.apache.lucene.luke.app.desktop.PreferencesFactory; |
| +import org.apache.lucene.luke.app.desktop.util.DialogOpener; |
| +import org.apache.lucene.luke.app.desktop.util.FontUtils; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.search.Explanation; |
| + |
| +/** Factory of explain dialog */ |
| +public final class ExplainDialogFactory implements DialogOpener.DialogFactory { |
| + |
| + private static ExplainDialogFactory instance; |
| + |
| + private final Preferences prefs; |
| + |
| + private JDialog dialog; |
| + |
| + private int docid = -1; |
| + |
| + private Explanation explanation; |
| + |
| + public synchronized static ExplainDialogFactory getInstance() throws IOException { |
| + if (instance == null) { |
| + instance = new ExplainDialogFactory(); |
| + } |
| + return instance; |
| + } |
| + |
| + private ExplainDialogFactory() throws IOException { |
| + this.prefs = PreferencesFactory.getInstance(); |
| + } |
| + |
| + public void setDocid(int docid) { |
| + this.docid = docid; |
| + } |
| + |
| + 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 00000000000..7af5fb1f80b |
| --- /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 00000000000..54451beaae2 |
| --- /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<Integer> deletedIndexes); |
| + |
| + void updateTokenFilters(List<Integer> deletedIndexes); |
| + |
| + Map<String, String> getCharFilterParams(int index); |
| + |
| + void updateCharFilterParams(int index, Map<String, String> updatedParams); |
| + |
| + void updateTokenizerParams(Map<String, String> updatedParams); |
| + |
| + Map<String, String> getTokenFilterParams(int index); |
| + |
| + void updateTokenFilterParams(int index, Map<String, String> 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 00000000000..4b1bc22fcf8 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/analysis/CustomAnalyzerPanelProvider.java |
| @@ -0,0 +1,751 @@ |
| +/* |
| + * 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.io.IOException; |
| +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 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 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<String> selectedCfList = new JList<>(new String[]{}); |
| + |
| + private final JButton cfEditBtn = new JButton(); |
| + |
| + private final JComboBox<String> cfFactoryCombo = new JComboBox<>(); |
| + |
| + private final JTextField selectedTokTF = new JTextField(); |
| + |
| + private final JButton tokEditBtn = new JButton(); |
| + |
| + private final JComboBox<String> tokFactoryCombo = new JComboBox<>(); |
| + |
| + private final JList<String> selectedTfList = new JList<>(new String[]{}); |
| + |
| + private final JButton tfEditBtn = new JButton(); |
| + |
| + private final JComboBox<String> tfFactoryCombo = new JComboBox<>(); |
| + |
| + private final ListenerFunctions listeners = new ListenerFunctions(); |
| + |
| + private final List<Map<String, String>> cfParamsList = new ArrayList<>(); |
| + |
| + private final Map<String, String> tokParams = new HashMap<>(); |
| + |
| + private final List<Map<String, String>> tfParamsList = new ArrayList<>(); |
| + |
| + private JPanel containerPanel; |
| + |
| + private Analysis analysisModel; |
| + |
| + public CustomAnalyzerPanelProvider() throws IOException { |
| + this.operatorRegistry = ComponentOperatorRegistry.getInstance(); |
| + this.editParamsDialogFactory = EditParamsDialogFactory.getInstance(); |
| + this.editFiltersDialogFactory = EditFiltersDialogFactory.getInstance(); |
| + this.messageBroker = MessageBroker.getInstance(); |
| + |
| + operatorRegistry.register(CustomAnalyzerPanelOperator.class, this); |
| + |
| + cfFactoryCombo.addActionListener(listeners::addCharFilter); |
| + tokFactoryCombo.addActionListener(listeners::setTokenizer); |
| + tfFactoryCombo.addActionListener(listeners::addTokenFilter); |
| + } |
| + |
| + 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<String> charFilters = ListUtils.getAllItems(selectedCfList); |
| + assert charFilters.size() == cfParamsList.size(); |
| + |
| + List<String> 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<String> 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<String> 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<String, String> 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<String> 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<String> filters = ListUtils.getAllItems(selectedTfList); |
| + showEditFiltersDialog(EditFiltersMode.TOKENFILTER, filters, |
| + () -> { |
| + tfEditBtn.setEnabled(selectedTfList.getModel().getSize() > 0); |
| + buildBtn.setEnabled(true); |
| + }); |
| + } |
| + |
| + private void showEditFiltersDialog(EditFiltersMode mode, List<String> 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<String> 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<String> 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<String> 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<Integer> deletedIndexes) { |
| + // update filters |
| + List<String> 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<Map<String, String>> 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<Integer> deletedIndexes) { |
| + // update filters |
| + List<String> 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<Map<String, String>> 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<String, String> getCharFilterParams(int index) { |
| + if (index < 0 || index > cfParamsList.size()) { |
| + throw new IllegalArgumentException(); |
| + } |
| + return Collections.unmodifiableMap(cfParamsList.get(index)); |
| + } |
| + |
| + @Override |
| + public void updateCharFilterParams(int index, Map<String, String> 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<String, String> updatedParams) { |
| + tokParams.clear(); |
| + tokParams.putAll(updatedParams); |
| + } |
| + |
| + @Override |
| + public Map<String, String> getTokenFilterParams(int index) { |
| + if (index < 0 || index > tfParamsList.size()) { |
| + throw new IllegalArgumentException(); |
| + } |
| + return Collections.unmodifiableMap(tfParamsList.get(index)); |
| + } |
| + |
| + @Override |
| + public void updateTokenFilterParams(int index, Map<String, String> 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 00000000000..856de6357e1 |
| --- /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<Class<? extends Analyzer>> presetAnalyzers); |
| + |
| + void setSelectedAnalyzer(Class<? extends Analyzer> 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 00000000000..f8210821a3a |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/analysis/PresetAnalyzerPanelProvider.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.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 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 PresetAnalyzerPanelOperator { |
| + |
| + private final ComponentOperatorRegistry operatorRegistry; |
| + |
| + private final JComboBox<String> analyzersCB = new JComboBox<>(); |
| + |
| + private final ListenerFunctions listeners = new ListenerFunctions(); |
| + |
| + public PresetAnalyzerPanelProvider() { |
| + this.operatorRegistry = ComponentOperatorRegistry.getInstance(); |
| + operatorRegistry.register(PresetAnalyzerPanelOperator.class, this); |
| + } |
| + |
| + 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); |
| + analyzersCB.setEnabled(false); |
| + center.add(analyzersCB); |
| + panel.add(center, BorderLayout.CENTER); |
| + |
| + return panel; |
| + } |
| + |
| + // control methods |
| + |
| + @Override |
| + public void setPresetAnalyzers(Collection<Class<? extends Analyzer>> presetAnalyzers) { |
| + String[] analyzerNames = presetAnalyzers.stream().map(Class::getName).toArray(String[]::new); |
| + ComboBoxModel<String> model = new DefaultComboBoxModel<>(analyzerNames); |
| + analyzersCB.setModel(model); |
| + analyzersCB.setEnabled(true); |
| + } |
| + |
| + @Override |
| + public void setSelectedAnalyzer(Class<? extends Analyzer> 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 00000000000..20cbe7b84f5 |
| --- /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 00000000000..382d73aaf69 |
| --- /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 00000000000..9f74a4df323 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/AnalyzerPaneProvider.java |
| @@ -0,0 +1,200 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components.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 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 */ |
| +public final class AnalyzerPaneProvider implements AnalyzerTabOperator { |
| + |
| + private final TabSwitcherProxy tabSwitcher; |
| + |
| + private final JLabel analyzerNameLbl = new JLabel(StandardAnalyzer.class.getName()); |
| + |
| + private final JList<String> charFilterList = new JList<>(); |
| + |
| + private final JTextField tokenizerTF = new JTextField(); |
| + |
| + private final JList<String> tokenFilterList = new JList<>(); |
| + |
| + public AnalyzerPaneProvider() { |
| + this.tabSwitcher = TabSwitcherProxy.getInstance(); |
| + |
| + ComponentOperatorRegistry.getInstance().register(AnalyzerTabOperator.class, this); |
| + } |
| + |
| + 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<String> charFilterListModel = new DefaultListModel<>(); |
| + customAnalyzer.getCharFilterFactories().stream() |
| + .map(f -> f.getClass().getSimpleName()) |
| + .forEach(charFilterListModel::addElement); |
| + charFilterList.setModel(charFilterListModel); |
| + |
| + tokenizerTF.setText(customAnalyzer.getTokenizerFactory().getClass().getSimpleName()); |
| + |
| + DefaultListModel<String> 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 00000000000..55aec09566f |
| --- /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 for 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 00000000000..1217bf90329 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/FieldValuesPaneProvider.java |
| @@ -0,0 +1,206 @@ |
| +/* |
| + * 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 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 */ |
| +public final class FieldValuesPaneProvider implements FieldValuesTabOperator { |
| + |
| + private final JCheckBox loadAllCB = new JCheckBox(); |
| + |
| + private final JTable fieldsTable = new JTable(); |
| + |
| + private ListenerFunctions listners = new ListenerFunctions(); |
| + |
| + public FieldValuesPaneProvider() { |
| + ComponentOperatorRegistry.getInstance().register(FieldValuesTabOperator.class, this); |
| + } |
| + |
| + 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.addActionListener(listners::loadAllFields); |
| + loadAllCB.setOpaque(false); |
| + 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<String> 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<String> getFieldsToLoad() { |
| + Set<String> 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); |
| + } |
| + } |
| + } |
| + } |
| + |
| + static final class FieldsTableModel extends TableModelBase<FieldsTableModel.Column> { |
| + |
| + 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<String> 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 00000000000..0b317651c06 |
| --- /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<String> fields); |
| + |
| + Set<String> 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 00000000000..ad791a40347 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/MLTPaneProvider.java |
| @@ -0,0 +1,303 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.components.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 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.components.fragments.search.FieldValuesPaneProvider.FieldsTableModel; |
| +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 */ |
| +public final class MLTPaneProvider implements 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(); |
| + |
| + public MLTPaneProvider() { |
| + this.tabSwitcher = TabSwitcherProxy.getInstance(); |
| + |
| + ComponentOperatorRegistry.getInstance().register(MLTTabOperator.class, this); |
| + } |
| + |
| + 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.addActionListener(listeners::loadAllFields); |
| + loadAllCB.setOpaque(false); |
| + 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<String> 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<String> 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, 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 == MLTFieldsTableModel.Column.SELECT.getIndex()) { |
| + boolean isLoad = (boolean) fieldsTable.getModel().getValueAt(row, col); |
| + if (!isLoad) { |
| + loadAllCB.setSelected(false); |
| + } |
| + } |
| + } |
| + } |
| + |
| + static final class MLTFieldsTableModel extends TableModelBase<MLTFieldsTableModel.Column> { |
| + |
| + 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<String> 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 00000000000..1180bc772d0 |
| --- /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<String> 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 00000000000..f565339853d |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/QueryParserPaneProvider.java |
| @@ -0,0 +1,513 @@ |
| +/* |
| + * 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 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; |
| + |
| +/** Provider of the QueryParser pane (tab) */ |
| +public final class QueryParserPaneProvider implements QueryParserTabOperator { |
| + |
| + private final JRadioButton standardRB = new JRadioButton(); |
| + |
| + private final JRadioButton classicRB = new JRadioButton(); |
| + |
| + private final JComboBox<String> dfCB = new JComboBox<>(); |
| + |
| + private final JComboBox<String> 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<String> dateResCB = 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(); |
| + |
| + public QueryParserPaneProvider() { |
| + ComponentOperatorRegistry.getInstance().register(QueryParserTabOperator.class, this); |
| + } |
| + |
| + 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.addActionListener(listeners::selectStandardQParser); |
| + standardRB.setOpaque(false); |
| + |
| + classicRB.setText("Classic QueryParser"); |
| + classicRB.addActionListener(listeners::selectClassicQparser); |
| + classicRB.setOpaque(false); |
| + |
| + 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(dateResCB::addItem); |
| + dateResCB.setSelectedItem(config.getDateResolution().name()); |
| + dateResCB.setOpaque(false); |
| + resolution.add(dateResCB); |
| + 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<String> searchableFields) { |
| + dfCB.removeAllItems(); |
| + for (String field : searchableFields) { |
| + dfCB.addItem(field); |
| + } |
| + } |
| + |
| + @Override |
| + public void setRangeSearchableFields(Collection<String> rangeSearchableFields) { |
| + pointRangeQueryTable.setModel(new PointTypesTableModel(rangeSearchableFields)); |
| + pointRangeQueryTable.setShowGrid(true); |
| + String[] numTypes = Arrays.stream(PointTypesTableModel.NumType.values()) |
| + .map(PointTypesTableModel.NumType::name) |
| + .toArray(String[]::new); |
| + JComboBox<String> 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(PointTypesTableModel.NumType.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<String, Class<? extends Number>> 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) dateResCB.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); |
| + } |
| + } |
| + |
| + } |
| + |
| + static final class PointTypesTableModel extends TableModelBase<PointTypesTableModel.Column> { |
| + |
| + 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<String> 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 00000000000..1a398721703 |
| --- /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 for the QueryParser tab */ |
| +public interface QueryParserTabOperator extends ComponentOperatorRegistry.ComponentOperator { |
| + void setSearchableFields(Collection<String> searchableFields); |
| + |
| + void setRangeSearchableFields(Collection<String> 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 00000000000..8c7cd114c69 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/SimilarityPaneProvider.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.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 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 */ |
| +public final class SimilarityPaneProvider implements 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(); |
| + |
| + public SimilarityPaneProvider() { |
| + ComponentOperatorRegistry.getInstance().register(SimilarityTabOperator.class, this); |
| + } |
| + |
| + 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.addActionListener(listeners::toggleTfIdf); |
| + tfidfCB.setOpaque(false); |
| + 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 00000000000..7ecd37117ac |
| --- /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 for 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 00000000000..d86215971a7 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/fragments/search/SortPaneProvider.java |
| @@ -0,0 +1,255 @@ |
| +/* |
| + * 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 org.apache.lucene.luke.app.desktop.components.ComponentOperatorRegistry; |
| +import org.apache.lucene.luke.app.desktop.components.SearchTabOperator; |
| +import org.apache.lucene.luke.app.desktop.util.MessageUtils; |
| +import org.apache.lucene.luke.app.desktop.util.StringUtils; |
| +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 */ |
| +public final class SortPaneProvider implements SortTabOperator { |
| + |
| + private static final String COMMAND_FIELD_COMBO1 = "fieldCombo1"; |
| + |
| + private static final String COMMAND_FIELD_COMBO2 = "fieldCombo2"; |
| + |
| + private final JComboBox<String> fieldCombo1 = new JComboBox<>(); |
| + |
| + private final JComboBox<String> typeCombo1 = new JComboBox<>(); |
| + |
| + private final JComboBox<String> orderCombo1 = new JComboBox<>(Order.names()); |
| + |
| + private final JComboBox<String> fieldCombo2 = new JComboBox<>(); |
| + |
| + private final JComboBox<String> typeCombo2 = new JComboBox<>(); |
| + |
| + private final JComboBox<String> orderCombo2 = new JComboBox<>(Order.names()); |
| + |
| + private final ListenerFunctions listeners = new ListenerFunctions(); |
| + |
| + private final ComponentOperatorRegistry operatorRegistry; |
| + |
| + private Search searchModel; |
| + |
| + public SortPaneProvider() { |
| + this.operatorRegistry = ComponentOperatorRegistry.getInstance(); |
| + operatorRegistry.register(SortTabOperator.class, this); |
| + } |
| + |
| + 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("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<String> sortableFields) { |
| + fieldCombo1.removeAllItems(); |
| + fieldCombo2.removeAllItems(); |
| + |
| + fieldCombo1.addItem(""); |
| + fieldCombo2.addItem(""); |
| + |
| + for (String field : sortableFields) { |
| + fieldCombo1.addItem(field); |
| + fieldCombo2.addItem(field); |
| + } |
| + } |
| + |
| + @Override |
| + public Sort getSort() { |
| + if (StringUtils.isNullOrEmpty((String) fieldCombo1.getSelectedItem()) |
| + && StringUtils.isNullOrEmpty((String) fieldCombo2.getSelectedItem())) { |
| + return null; |
| + } |
| + |
| + List<SortField> li = new ArrayList<>(); |
| + if (!StringUtils.isNullOrEmpty((String) fieldCombo1.getSelectedItem())) { |
| + searchModel.getSortType((String) fieldCombo1.getSelectedItem(), (String) typeCombo1.getSelectedItem(), isReverse(orderCombo1)).ifPresent(li::add); |
| + } |
| + if (!StringUtils.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<String> 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); |
| + } |
| + resetExactHitsCnt(); |
| + } |
| + |
| + private void resetField(JComboBox<String> fieldCombo, JComboBox<String> typeCombo, JComboBox<String> orderCombo) { |
| + typeCombo.removeAllItems(); |
| + if (StringUtils.isNullOrEmpty((String) fieldCombo.getSelectedItem())) { |
| + typeCombo.addItem(""); |
| + typeCombo.setEnabled(false); |
| + orderCombo.setEnabled(false); |
| + } else { |
| + List<SortField> 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); |
| + |
| + resetExactHitsCnt(); |
| + } |
| + |
| + private void resetExactHitsCnt() { |
| + operatorRegistry.get(SearchTabOperator.class).ifPresent(operator -> { |
| + if (StringUtils.isNullOrEmpty((String) fieldCombo1.getSelectedItem()) && |
| + StringUtils.isNullOrEmpty((String) fieldCombo2.getSelectedItem())) { |
| + operator.enableExactHitsCB(true); |
| + operator.setExactHits(false); |
| + } else { |
| + operator.enableExactHitsCB(false); |
| + operator.setExactHits(true); |
| + } |
| + }); |
| + } |
| + } |
| + |
| + 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 00000000000..bdaa027cc60 |
| --- /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 for the Sort tab */ |
| +public interface SortTabOperator extends ComponentOperatorRegistry.ComponentOperator { |
| + void setSearchModel(Search model); |
| + |
| + void setSortableFields(Collection<String> 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 00000000000..dfa87f59cb4 |
| --- /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 tabs */ |
| +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 00000000000..fefd0c88925 |
| --- /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 00000000000..44162a07d80 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/dto/documents/NewField.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.dto.documents; |
| + |
| +import java.util.Objects; |
| + |
| +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.IndexableField; |
| +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<? extends IndexableField> 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 boolean deletedProperty() { |
| + return deleted; |
| + } |
| + |
| + public void setDeleted(boolean value) { |
| + deleted = value; |
| + } |
| + |
| + public String getName() { |
| + return name; |
| + } |
| + |
| + public void setName(String name) { |
| + this.name = Objects.requireNonNull(name); |
| + } |
| + |
| + public Class<? extends IndexableField> getTypeProperty() { |
| + return type; |
| + } |
| + |
| + public Class<? extends IndexableField> getType() { |
| + return type; |
| + } |
| + |
| + public void setType(Class<? extends IndexableField> type) { |
| + this.type = Objects.requireNonNull(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(String value) { |
| + this.value = Objects.requireNonNull(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 00000000000..0f08238ddd1 |
| --- /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 00000000000..c4c36bd22a9 |
| --- /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. |
| + */ |
| + |
| +/** Views (UIs) for Luke */ |
| +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 00000000000..49e1b4f6c59 |
| --- /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<T extends DialogOpener.DialogFactory> { |
| + |
| + private final T factory; |
| + |
| + public DialogOpener(T factory) { |
| + this.factory = factory; |
| + } |
| + |
| + public void open(String title, int width, int height, Consumer<? super T> initializer, |
| + String... styleSheets) { |
| + open(LukeMain.getOwnerFrame(), title, width, height, initializer, styleSheets); |
| + } |
| + |
| + public void open(Window owner, String title, int width, int height, Consumer<? super T> initializer, |
| + String... styleSheets) { |
| + initializer.accept(factory); |
| + JDialog dialog = factory.create(owner, title, width, height); |
| + dialog.setVisible(true); |
| + } |
| + |
| + /** factory interface to create a dialog */ |
| + 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 00000000000..b989748f52a |
| --- /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.logging.log4j.Logger; |
| +import org.apache.lucene.luke.app.desktop.MessageBroker; |
| +import org.apache.lucene.luke.models.LukeException; |
| +import org.apache.lucene.luke.util.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 00000000000..c4f47588815 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/FontUtils.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.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 { |
| + |
| + public static final String TTF_RESOURCE_NAME = "org/apache/lucene/luke/app/desktop/font/ElegantIcons.ttf"; |
| + |
| + @SuppressWarnings("unchecked") |
| + public static JLabel toLinkText(JLabel label) { |
| + label.setForeground(StyleConstants.LINK_COLOR); |
| + Font font = label.getFont(); |
| + Map<TextAttribute, Object> attributes = (Map<TextAttribute, Object>) 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(TTF_RESOURCE_NAME); |
| + 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 "<html><font face=\"ElegantIcons\">" + iconRef + "</font></html>"; |
| + } |
| + |
| + /** |
| + * Generates HTML text with embedded Elegant Icon Font. |
| + * |
| + * @param iconRef HTML numeric character reference of the icon |
| + * @param text - HTML text |
| + */ |
| + public static String elegantIconHtml(String iconRef, String text) { |
| + return "<html><font face=\"ElegantIcons\">" + iconRef + "</font> " + text + "</html>"; |
| + } |
| + |
| + 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 00000000000..41c7f079e50 |
| --- /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 00000000000..d7989f9353e |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/ImageUtils.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.util; |
| + |
| +import javax.swing.ImageIcon; |
| +import java.awt.Image; |
| + |
| +/** Image utilities */ |
| +public class ImageUtils { |
| + |
| + private static final String IMAGE_BASE_DIR = "org/apache/lucene/luke/app/desktop/img/"; |
| + |
| + public static ImageIcon createImageIcon(String name, int width, int height) { |
| + return createImageIcon(name, "", width, height); |
| + } |
| + |
| + public static ImageIcon createImageIcon(String name, String description, int width, int height) { |
| + java.net.URL imgURL = ImageUtils.class.getClassLoader().getResource(IMAGE_BASE_DIR + name); |
| + 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 00000000000..cc756eaffa3 |
| --- /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 <T> List<T> getAllItems(JList<T> jlist) { |
| + ListModel<T> model = jlist.getModel(); |
| + return getAllItems(jlist, model::getElementAt); |
| + } |
| + |
| + public static <T, R> List<R> getAllItems(JList<T> jlist, IntFunction<R> mapFunc) { |
| + ListModel<T> 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 00000000000..cc6989159c9 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/MessageUtils.java |
| @@ -0,0 +1,61 @@ |
| +/* |
| + * 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 final String MESSAGE_BUNDLE_BASENAME = "org/apache/lucene/luke/app/desktop/messages/messages"; |
| + |
| + 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(MESSAGE_BUNDLE_BASENAME, 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 00000000000..ae2ef5ac341 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/NumericUtils.java |
| @@ -0,0 +1,103 @@ |
| +/* |
| + * 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; |
| + |
| +/** Utilities for handling numeric values */ |
| +public class NumericUtils { |
| + |
| + public static int[] convertToIntArray(String value, boolean ignoreException) throws NumberFormatException { |
| + if (StringUtils.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 (StringUtils.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 (StringUtils.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 (StringUtils.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/StringUtils.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/StringUtils.java |
| new file mode 100644 |
| index 00000000000..23a4f79c2d4 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/StringUtils.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.util; |
| + |
| +import java.util.Objects; |
| + |
| +/** Utilities for handling strings */ |
| +public class StringUtils { |
| + |
| + public static boolean isNullOrEmpty(String s) { |
| + return Objects.isNull(s) || s.equals(""); |
| + } |
| + |
| + private StringUtils() { |
| + } |
| +} |
| 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 00000000000..3b70265cf87 |
| --- /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 00000000000..c3dc7a1e479 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TabUtils.java |
| @@ -0,0 +1,41 @@ |
| +/* |
| + * 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; |
| + |
| +/** Tab utilities */ |
| +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 00000000000..cea72aea1fd |
| --- /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 <T extends TableColumnInfo> String[] columnNames(T[] columns) { |
| + return columnMap(columns).entrySet().stream().map(e -> e.getValue().getColName()).toArray(String[]::new); |
| + } |
| + |
| + public static <T extends TableColumnInfo> TreeMap<Integer, T> 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 00000000000..b7b1d421383 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TextAreaAppender.java |
| @@ -0,0 +1,102 @@ |
| +/* |
| + * 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 javax.swing.SwingUtilities; |
| +import java.io.Serializable; |
| +import java.util.concurrent.locks.Lock; |
| +import java.util.concurrent.locks.ReadWriteLock; |
| +import java.util.concurrent.locks.ReentrantReadWriteLock; |
| + |
| +import org.apache.logging.log4j.core.Appender; |
| +import org.apache.logging.log4j.core.Core; |
| +import org.apache.logging.log4j.core.Filter; |
| +import org.apache.logging.log4j.core.LogEvent; |
| +import org.apache.logging.log4j.core.StringLayout; |
| +import org.apache.logging.log4j.core.appender.AbstractAppender; |
| +import org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender; |
| +import org.apache.logging.log4j.core.config.Property; |
| +import org.apache.logging.log4j.core.config.plugins.Plugin; |
| +import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; |
| + |
| +/** Log appender for text areas */ |
| +@Plugin(name = "TextArea", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) |
| +public final class TextAreaAppender extends AbstractAppender { |
| + |
| + private static JTextArea textArea; |
| + |
| + private static final ReadWriteLock rwLock = new ReentrantReadWriteLock(); |
| + private static final Lock readLock = rwLock.readLock(); |
| + private static final Lock writeLock = rwLock.writeLock(); |
| + |
| + protected TextAreaAppender(String name, Filter filter, |
| + org.apache.logging.log4j.core.Layout<? extends Serializable> layout, final boolean ignoreExceptions) { |
| + super(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY); |
| + } |
| + |
| + public static void setTextArea(JTextArea ta) { |
| + writeLock.lock(); |
| + try { |
| + if (textArea != null) { |
| + throw new IllegalStateException("TextArea already set."); |
| + } |
| + textArea = ta; |
| + } finally { |
| + writeLock.unlock(); |
| + } |
| + } |
| + |
| + @Override |
| + public void append(LogEvent event) { |
| + readLock.lock(); |
| + try { |
| + if (textArea == null) { |
| + // just ignore any events logged before the area is available |
| + return; |
| + } |
| + |
| + final String message = ((StringLayout) getLayout()).toSerializable(event); |
| + SwingUtilities.invokeLater(() -> { |
| + textArea.append(message); |
| + }); |
| + } finally { |
| + readLock.unlock(); |
| + } |
| + } |
| + |
| + /** |
| + * Builds TextAreaAppender instances. |
| + * |
| + * @param <B> The type to build |
| + */ |
| + public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B> |
| + implements org.apache.logging.log4j.core.util.Builder<TextAreaAppender> { |
| + |
| + @Override |
| + public TextAreaAppender build() { |
| + return new TextAreaAppender(getName(), getFilter(), getOrCreateLayout(), true); |
| + } |
| + } |
| + |
| + @PluginBuilderFactory |
| + public static <B extends Builder<B>> B newBuilder() { |
| + return new Builder<B>().asBuilder(); |
| + } |
| + |
| +} |
| 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 00000000000..7c1f7caad2c |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TextAreaPrintStream.java |
| @@ -0,0 +1,50 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.app.desktop.util; |
| + |
| +import javax.swing.JTextArea; |
| +import java.io.ByteArrayOutputStream; |
| +import java.io.PrintStream; |
| +import java.io.UnsupportedEncodingException; |
| +import java.nio.charset.StandardCharsets; |
| + |
| +/** PrintStream for text areas */ |
| +public final class TextAreaPrintStream extends PrintStream { |
| + |
| + private final ByteArrayOutputStream baos; |
| + |
| + private final JTextArea textArea; |
| + |
| + public TextAreaPrintStream(JTextArea textArea) throws UnsupportedEncodingException { |
| + super(new ByteArrayOutputStream(), false, StandardCharsets.UTF_8.name()); // TODO: replace by Charset in Java 11 |
| + this.baos = (ByteArrayOutputStream) out; |
| + this.textArea = textArea; |
| + baos.reset(); |
| + } |
| + |
| + @Override |
| + public void flush() { |
| + try { |
| + textArea.append(baos.toString(StandardCharsets.UTF_8.name())); // TODO: replace by Charset in Java 11 |
| + } catch (UnsupportedEncodingException e) { |
| + setError(); |
| + } 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 00000000000..4b6e71bf0fe |
| --- /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/inifile/IniFile.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/IniFile.java |
| new file mode 100644 |
| index 00000000000..fd723ba78b7 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/IniFile.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.util.inifile; |
| + |
| +import java.io.IOException; |
| +import java.nio.file.Path; |
| + |
| +/** Interface representing ini files */ |
| +public interface IniFile { |
| + |
| + void load(Path path) throws IOException; |
| + |
| + void store(Path path) throws IOException; |
| + |
| + void put(String section, String option, Object value); |
| + |
| + String getString(String section, String option); |
| + |
| + Boolean getBoolean(String section, String option); |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/IniFileReader.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/IniFileReader.java |
| new file mode 100644 |
| index 00000000000..21bb85ada49 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/IniFileReader.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.util.inifile; |
| + |
| +import java.io.IOException; |
| +import java.nio.file.Path; |
| +import java.util.Map; |
| + |
| +/** ini files interface */ |
| +public interface IniFileReader { |
| + |
| + Map<String, OptionMap> readSections(Path path) throws IOException; |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/IniFileWriter.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/IniFileWriter.java |
| new file mode 100644 |
| index 00000000000..9977046e3a7 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/IniFileWriter.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.util.inifile; |
| + |
| +import java.io.IOException; |
| +import java.nio.file.Path; |
| +import java.util.Map; |
| + |
| +/** ini files writer */ |
| +public interface IniFileWriter { |
| + |
| + void writeSections(Path path, Map<String, OptionMap> sections) throws IOException; |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/OptionMap.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/OptionMap.java |
| new file mode 100644 |
| index 00000000000..f7783d70609 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/OptionMap.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.util.inifile; |
| + |
| +import java.util.LinkedHashMap; |
| + |
| +/** Key-value store for options */ |
| +public class OptionMap extends LinkedHashMap<String, String> { |
| + |
| + String getAsString(String key) { |
| + return get(key); |
| + } |
| + |
| + Boolean getAsBoolean(String key) { |
| + return Boolean.parseBoolean(get(key)); |
| + } |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/SimpleIniFile.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/SimpleIniFile.java |
| new file mode 100644 |
| index 00000000000..3c539f81f7c |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/SimpleIniFile.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.app.desktop.util.inifile; |
| + |
| +import java.io.IOException; |
| +import java.nio.file.Path; |
| +import java.util.LinkedHashMap; |
| +import java.util.Map; |
| +import java.util.Objects; |
| + |
| +/** Simple implementation of {@link IniFile} */ |
| +public class SimpleIniFile implements IniFile { |
| + |
| + private final Map<String, OptionMap> sections = new LinkedHashMap<>(); |
| + |
| + private IniFileWriter writer = new SimpleIniFileWriter(); |
| + |
| + private IniFileReader reader = new SimpleIniFileReader(); |
| + |
| + @Override |
| + public synchronized void load(Path path) throws IOException { |
| + sections.putAll(reader.readSections(path)); |
| + } |
| + |
| + @Override |
| + public synchronized void store(Path path) throws IOException { |
| + writer.writeSections(path, sections); |
| + } |
| + |
| + @Override |
| + public synchronized void put(String section, String option, Object value) { |
| + if (checkString(section) && checkString(option) && Objects.nonNull(value)) { |
| + sections.putIfAbsent(section, new OptionMap()); |
| + sections.get(section).put(option, (value instanceof String) ? (String) value : String.valueOf(value)); |
| + } |
| + } |
| + |
| + @Override |
| + public String getString(String section, String option) { |
| + if (checkString(section) && checkString(option)) { |
| + OptionMap options = sections.get(section); |
| + if (options != null) { |
| + return options.getAsString(option); |
| + } |
| + } |
| + return null; |
| + } |
| + |
| + @Override |
| + public Boolean getBoolean(String section, String option) { |
| + if (checkString(section) && checkString(option)) { |
| + OptionMap options = sections.get(section); |
| + if (options != null) { |
| + return options.getAsBoolean(option); |
| + } |
| + } |
| + return false; |
| + } |
| + |
| + private boolean checkString(String s) { |
| + return Objects.nonNull(s) && !s.equals(""); |
| + } |
| + |
| + Map<String, OptionMap> getSections() { |
| + return sections; |
| + } |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/SimpleIniFileReader.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/SimpleIniFileReader.java |
| new file mode 100644 |
| index 00000000000..00a03636040 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/SimpleIniFileReader.java |
| @@ -0,0 +1,63 @@ |
| +/* |
| + * 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.inifile; |
| + |
| +import java.io.BufferedReader; |
| +import java.io.IOException; |
| +import java.nio.charset.StandardCharsets; |
| +import java.nio.file.Files; |
| +import java.nio.file.Path; |
| +import java.util.LinkedHashMap; |
| +import java.util.Map; |
| + |
| +/** Simple implementation of {@link IniFileReader} */ |
| +public class SimpleIniFileReader implements IniFileReader { |
| + |
| + private String currentSection = ""; |
| + |
| + @Override |
| + public Map<String, OptionMap> readSections(Path path) throws IOException { |
| + final Map<String, OptionMap> sections = new LinkedHashMap<>(); |
| + |
| + try (BufferedReader r = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { |
| + r.lines().forEach(line -> { |
| + line = line.trim(); |
| + |
| + if (isSectionLine(line)) { |
| + // set section if this is a valid section string |
| + currentSection = line.substring(1, line.length()-1); |
| + sections.putIfAbsent(currentSection, new OptionMap()); |
| + } else if (!currentSection.equals("")) { |
| + // put option if this is a valid option string |
| + String[] ary = line.split("=", 2); |
| + if (ary.length == 2 && !ary[0].trim().equals("") && !ary[1].trim().equals("")) { |
| + sections.get(currentSection).put(ary[0].trim(), ary[1].trim()); |
| + } |
| + } |
| + |
| + }); |
| + } |
| + return sections; |
| + } |
| + |
| + private boolean isSectionLine(String line) { |
| + return line.startsWith("[") && line.endsWith("]") |
| + && line.substring(1, line.length()-1).matches("^[a-zA-Z0-9]+$"); |
| + } |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/SimpleIniFileWriter.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/SimpleIniFileWriter.java |
| new file mode 100644 |
| index 00000000000..ae03bf635c4 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/SimpleIniFileWriter.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.desktop.util.inifile; |
| + |
| +import java.io.BufferedWriter; |
| +import java.io.IOException; |
| +import java.nio.charset.StandardCharsets; |
| +import java.nio.file.Files; |
| +import java.nio.file.Path; |
| +import java.util.Map; |
| + |
| +/** Simple implementation of {@link IniFileWriter} */ |
| +public class SimpleIniFileWriter implements IniFileWriter { |
| + |
| + @Override |
| + public void writeSections(Path path, Map<String, OptionMap> sections) throws IOException { |
| + try (BufferedWriter w = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) { |
| + for (Map.Entry<String, OptionMap> section : sections.entrySet()) { |
| + w.write("[" + section.getKey() + "]"); |
| + w.newLine(); |
| + |
| + for (Map.Entry<String, String> option : section.getValue().entrySet()) { |
| + w.write(option.getKey() + " = " + option.getValue()); |
| + w.newLine(); |
| + } |
| + |
| + w.newLine(); |
| + } |
| + w.flush(); |
| + } |
| + } |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/package-info.java |
| new file mode 100644 |
| index 00000000000..d03b86fa42f |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/inifile/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. |
| + */ |
| + |
| +/** Ini file parser / writer */ |
| +package org.apache.lucene.luke.app.desktop.util.inifile; |
| \ No newline at end of file |
| 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 00000000000..f5ddf2fee88 |
| --- /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 00000000000..5cf30577ae9 |
| --- /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 00000000000..bd43e1e5f96 |
| --- /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 00000000000..8e7ea9e4f4d |
| --- /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 00000000000..d8bcbfa34ae |
| --- /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 00000000000..524426cfc5a |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/LukeModel.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; |
| + |
| +import java.io.IOException; |
| +import java.util.Collection; |
| +import java.util.Objects; |
| + |
| +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; |
| +import org.apache.lucene.util.Bits; |
| + |
| +/** |
| + * 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 Bits liveDocs; |
| + |
| + protected IndexCommit commit; |
| + |
| + protected LukeModel(IndexReader reader) { |
| + this.reader = Objects.requireNonNull(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; |
| + } |
| + |
| + this.liveDocs = IndexUtils.getLiveDocs(reader); |
| + } |
| + |
| + protected LukeModel (Directory dir) { |
| + this.dir = Objects.requireNonNull(dir); |
| + } |
| + |
| + public Collection<String> 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 00000000000..8b640ee2dd0 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/analysis/Analysis.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.analysis; |
| + |
| +import java.util.Collection; |
| +import java.util.Collections; |
| +import java.util.List; |
| +import java.util.Map; |
| +import java.util.Objects; |
| + |
| +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<TokenAttribute> attributes; |
| + |
| + Token(String term, List<TokenAttribute> attributes) { |
| + this.term = Objects.requireNonNull(term); |
| + this.attributes = Objects.requireNonNull(attributes); |
| + } |
| + |
| + /** |
| + * Returns the string representation of this token. |
| + */ |
| + public String getTerm() { |
| + return term; |
| + } |
| + |
| + /** |
| + * Returns attributes of this token. |
| + */ |
| + public List<TokenAttribute> getAttributes() { |
| + return Collections.unmodifiableList(attributes); |
| + } |
| + } |
| + |
| + /** |
| + * Holder for a token attribute. |
| + */ |
| + class TokenAttribute { |
| + private final String attClass; |
| + private final Map<String, String> attValues; |
| + |
| + TokenAttribute(String attClass, Map<String, String> attValues) { |
| + this.attClass = Objects.requireNonNull(attClass); |
| + this.attValues = Objects.requireNonNull(attValues); |
| + } |
| + |
| + /** |
| + * Returns attribute class name. |
| + */ |
| + public String getAttClass() { |
| + return attClass; |
| + } |
| + |
| + /** |
| + * Returns value of this attribute. |
| + */ |
| + public Map<String, String> getAttValues() { |
| + return Collections.unmodifiableMap(attValues); |
| + } |
| + } |
| + |
| + /** |
| + * Returns built-in {@link Analyzer}s. |
| + */ |
| + Collection<Class<? extends Analyzer>> getPresetAnalyzerTypes(); |
| + |
| + /** |
| + * Returns available char filter names. |
| + */ |
| + Collection<String> getAvailableCharFilters(); |
| + |
| + /** |
| + * Returns available tokenizer names. |
| + */ |
| + Collection<String> getAvailableTokenizers(); |
| + |
| + /** |
| + * Returns available token filter names. |
| + */ |
| + Collection<String> 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<Token> 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<String> 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 00000000000..8fa49c6162c |
| --- /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 00000000000..7d76b8f32a8 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/analysis/AnalysisImpl.java |
| @@ -0,0 +1,217 @@ |
| +/* |
| + * 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.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.Collections; |
| +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.Objects; |
| +import java.util.Set; |
| +import java.util.stream.Collectors; |
| + |
| +import org.apache.lucene.analysis.Analyzer; |
| +import org.apache.lucene.analysis.TokenStream; |
| +import org.apache.lucene.analysis.custom.CustomAnalyzer; |
| +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.luke.util.reflection.ClassScanner; |
| +import org.apache.lucene.util.AttributeImpl; |
| + |
| +/** Default implementation of {@link AnalysisImpl} */ |
| +public final class AnalysisImpl implements Analysis { |
| + |
| + private List<Class<? extends Analyzer>> presetAnalyzerTypes; |
| + |
| + private Analyzer analyzer; |
| + |
| + @Override |
| + public void addExternalJars(List<String> jarFiles) { |
| + List<URL> 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]), this.getClass().getClassLoader()); |
| + CharFilterFactory.reloadCharFilters(classLoader); |
| + TokenizerFactory.reloadTokenizers(classLoader); |
| + TokenFilterFactory.reloadTokenFilters(classLoader); |
| + } |
| + |
| + @Override |
| + public Collection<Class<? extends Analyzer>> getPresetAnalyzerTypes() { |
| + if (Objects.isNull(presetAnalyzerTypes)) { |
| + List<Class<? extends Analyzer>> types = new ArrayList<>(); |
| + for (Class<? extends Analyzer> clazz : getInstantiableSubTypesBuiltIn(Analyzer.class)) { |
| + try { |
| + // add to presets if no args constructor is available |
| + clazz.getConstructor(); |
| + types.add(clazz); |
| + } catch (NoSuchMethodException e) { |
| + } |
| + } |
| + presetAnalyzerTypes = Collections.unmodifiableList(types); |
| + } |
| + return presetAnalyzerTypes; |
| + } |
| + |
| + @Override |
| + public Collection<String> getAvailableCharFilters() { |
| + return CharFilterFactory.availableCharFilters().stream().sorted().collect(Collectors.toList()); |
| + } |
| + |
| + @Override |
| + public Collection<String> getAvailableTokenizers() { |
| + return TokenizerFactory.availableTokenizers().stream().sorted().collect(Collectors.toList()); |
| + } |
| + |
| + @Override |
| + public Collection<String> getAvailableTokenFilters() { |
| + return TokenFilterFactory.availableTokenFilters().stream().sorted().collect(Collectors.toList()); |
| + } |
| + |
| + private <T> List<Class<? extends T>> getInstantiableSubTypesBuiltIn(Class<T> superType) { |
| + ClassScanner scanner = new ClassScanner("org.apache.lucene.analysis", getClass().getClassLoader()); |
| + Set<Class<? extends T>> types = scanner.scanSubTypes(superType); |
| + return types.stream() |
| + .filter(type -> !Modifier.isAbstract(type.getModifiers())) |
| + .filter(type -> !type.getSimpleName().startsWith("Mock")) |
| + .sorted(Comparator.comparing(Class::getName)) |
| + .collect(Collectors.toList()); |
| + } |
| + |
| + @Override |
| + public List<Token> analyze(String text) { |
| + Objects.requireNonNull(text); |
| + |
| + if (analyzer == null) { |
| + throw new LukeException("Analyzer is not set."); |
| + } |
| + |
| + try { |
| + List<Token> result = new ArrayList<>(); |
| + |
| + TokenStream stream = analyzer.tokenStream("", text); |
| + stream.reset(); |
| + |
| + CharTermAttribute charAtt = stream.getAttribute(CharTermAttribute.class); |
| + |
| + // iterate tokens |
| + while (stream.incrementToken()) { |
| + List<TokenAttribute> attributes = new ArrayList<>(); |
| + Iterator<AttributeImpl> itr = stream.getAttributeImplsIterator(); |
| + |
| + while (itr.hasNext()) { |
| + AttributeImpl att = itr.next(); |
| + Map<String, String> 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(String analyzerType) { |
| + Objects.requireNonNull(analyzerType); |
| + |
| + try { |
| + Class<? extends Analyzer> 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(CustomAnalyzerConfig config) { |
| + Objects.requireNonNull(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 00000000000..1ffe2431852 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/analysis/CustomAnalyzerConfig.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.analysis; |
| + |
| +import java.util.ArrayList; |
| +import java.util.Collections; |
| +import java.util.HashMap; |
| +import java.util.List; |
| +import java.util.Map; |
| +import java.util.Objects; |
| +import java.util.Optional; |
| + |
| +/** |
| + * Configurations for a custom analyzer. |
| + */ |
| +public final class CustomAnalyzerConfig { |
| + |
| + private final String configDir; |
| + |
| + private final ComponentConfig tokenizerConfig; |
| + |
| + private final List<ComponentConfig> charFilterConfigs; |
| + |
| + private final List<ComponentConfig> tokenFilterConfigs; |
| + |
| + /** Builder for {@link CustomAnalyzerConfig} */ |
| + public static class Builder { |
| + private String configDir; |
| + private final ComponentConfig tokenizerConfig; |
| + private final List<ComponentConfig> charFilterConfigs = new ArrayList<>(); |
| + private final List<ComponentConfig> tokenFilterConfigs = new ArrayList<>(); |
| + |
| + public Builder(String name, Map<String, String> tokenizerParams) { |
| + Objects.requireNonNull(name); |
| + Objects.requireNonNull(tokenizerParams); |
| + tokenizerConfig = new ComponentConfig(name, new HashMap<>(tokenizerParams)); |
| + } |
| + |
| + public Builder configDir(String val) { |
| + configDir = val; |
| + return this; |
| + } |
| + |
| + public Builder addCharFilterConfig(String name, Map<String, String> params) { |
| + Objects.requireNonNull(name); |
| + Objects.requireNonNull(params); |
| + charFilterConfigs.add(new ComponentConfig(name, new HashMap<>(params))); |
| + return this; |
| + } |
| + |
| + public Builder addTokenFilterConfig(String name, Map<String, String> params) { |
| + Objects.requireNonNull(name); |
| + Objects.requireNonNull(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<String> getConfigDir() { |
| + return Optional.ofNullable(configDir); |
| + } |
| + |
| + /** |
| + * Returns Tokenizer configurations. |
| + */ |
| + ComponentConfig getTokenizerConfig() { |
| + return tokenizerConfig; |
| + } |
| + |
| + /** |
| + * Returns CharFilters configurations. |
| + */ |
| + List<ComponentConfig> getCharFilterConfigs() { |
| + return Collections.unmodifiableList(charFilterConfigs); |
| + } |
| + |
| + /** |
| + * Returns TokenFilters configurations. |
| + */ |
| + List<ComponentConfig> getTokenFilterConfigs() { |
| + return Collections.unmodifiableList(tokenFilterConfigs); |
| + } |
| + |
| + static class ComponentConfig { |
| + |
| + /* SPI name */ |
| + private final String name; |
| + /* parameter map */ |
| + private final Map<String, String> params; |
| + |
| + ComponentConfig(String name, Map<String, String> params) { |
| + this.name = Objects.requireNonNull(name); |
| + this.params = Objects.requireNonNull(params); |
| + } |
| + |
| + String getName() { |
| + return this.name; |
| + } |
| + |
| + Map<String, String> 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 00000000000..52a9c0c087d |
| --- /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 00000000000..73f1594a11c |
| --- /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 00000000000..dbd8abe17cf |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/commits/Commits.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.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<Commit> listCommits(); |
| + |
| + /** |
| + * Returns a commit of the specified generation. |
| + * @param commitGen - generation |
| + * @throws LukeException - if an internal error occurs when accessing index |
| + */ |
| + Optional<Commit> getCommit(long commitGen); |
| + |
| + /** |
| + * Returns index files for the specified generation. |
| + * @param commitGen - generation |
| + * @throws LukeException - if an internal error occurs when accessing index |
| + */ |
| + List<File> getFiles(long commitGen); |
| + |
| + /** |
| + * Returns segments for the specified generation. |
| + * @param commitGen - generation |
| + * @throws LukeException - if an internal error occurs when accessing index |
| + */ |
| + List<Segment> 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<String, String> 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<String, String> 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<Codec> 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 00000000000..22d959d8621 |
| --- /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 00000000000..d29fecc0e33 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/commits/CommitsImpl.java |
| @@ -0,0 +1,224 @@ |
| +/* |
| + * 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 org.apache.logging.log4j.Logger; |
| +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.luke.util.LoggerFactory; |
| +import org.apache.lucene.store.Directory; |
| + |
| +/** 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<Long, IndexCommit> 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<Long, IndexCommit> initCommitMap() { |
| + try { |
| + List<IndexCommit> indexCommits = DirectoryReader.listCommits(dir); |
| + Map<Long, IndexCommit> 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<Commit> listCommits() throws LukeException { |
| + List<Commit> commits = getCommitMap().values().stream() |
| + .map(Commit::of) |
| + .collect(Collectors.toList()); |
| + Collections.reverse(commits); |
| + return commits; |
| + } |
| + |
| + @Override |
| + public Optional<Commit> 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<File> 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<Segment> 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<String, String> 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<String, String> 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<Codec> 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<Long, IndexCommit> getCommitMap() throws LukeException { |
| + if (dir == null) { |
| + return Collections.emptyMap(); |
| + } |
| + return Collections.unmodifiableMap(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 00000000000..8038b39be3b |
| --- /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 00000000000..cea86e2ec9f |
| --- /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 00000000000..87ed8a0158d |
| --- /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 00000000000..ac1eff7e5ef |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocValues.java |
| @@ -0,0 +1,84 @@ |
| +/* |
| + * 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<BytesRef> values; |
| + |
| + private final List<Long> 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<BytesRef> values, List<Long> numericValues) { |
| + return new DocValues(dvType, values, numericValues); |
| + } |
| + |
| + private DocValues(DocValuesType dvType, List<BytesRef> values, List<Long> 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<BytesRef> getValues() { |
| + return values; |
| + } |
| + |
| + /** |
| + * Returns the list of numeric values. |
| + */ |
| + public List<Long> 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 00000000000..79a87e18099 |
| --- /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 java.io.IOException; |
| +import java.util.ArrayList; |
| +import java.util.Collections; |
| +import java.util.List; |
| +import java.util.Objects; |
| +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(IndexReader reader) { |
| + this.reader = Objects.requireNonNull(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<DocValues> 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<DocValues> 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<DocValues> 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<DocValues> createSortedNumericDocValues(int docid, String field, DocValuesType dvType) |
| + throws IOException { |
| + SortedNumericDocValues snvalues = IndexUtils.getSortedNumericDocValues(reader, field); |
| + |
| + if (snvalues.advanceExact(docid)) { |
| + List<Long> 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<DocValues> 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<DocValues> createSortedSetDocValues(int docid, String field, DocValuesType dvType) |
| + throws IOException { |
| + SortedSetDocValues ssvalues = IndexUtils.getSortedSetDocvalues(reader, field); |
| + |
| + if (ssvalues.advanceExact(docid)) { |
| + List<BytesRef> 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 00000000000..5c18d3054e9 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocumentField.java |
| @@ -0,0 +1,169 @@ |
| +/* |
| + * 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.Objects; |
| + |
| +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(FieldInfo finfo, IndexReader reader, int docId) |
| + throws IOException { |
| + return of(finfo, null, reader, docId); |
| + } |
| + |
| + static DocumentField of(FieldInfo finfo, IndexableField field, IndexReader reader, int docId) |
| + throws IOException { |
| + |
| + Objects.requireNonNull(finfo); |
| + Objects.requireNonNull(reader); |
| + |
| + 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 00000000000..d3735412e21 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/Documents.java |
| @@ -0,0 +1,143 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.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<String> getFieldNames(); |
| + |
| + /** |
| + * Returns true if the document with the specified <code>docid</code> 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<DocumentField> 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<Term> 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<Term> 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<Term> 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<Integer> 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<Integer> nextTermDoc(); |
| + |
| + /** |
| + * Returns the list of the position information for the current posting. |
| + * |
| + * @throws LukeException - if an internal error occurs when accessing index |
| + */ |
| + List<TermPosting> 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<Integer> 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<TermVectorEntry> 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<DocValues> 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 00000000000..96b0a6fb6e9 |
| --- /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 00000000000..e4b25296fb4 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocumentsImpl.java |
| @@ -0,0 +1,347 @@ |
| +/* |
| + * 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.lang.invoke.MethodHandles; |
| +import java.util.ArrayList; |
| +import java.util.Collections; |
| +import java.util.List; |
| +import java.util.Locale; |
| +import java.util.Objects; |
| +import java.util.Optional; |
| + |
| +import org.apache.logging.log4j.Logger; |
| +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.models.util.IndexUtils; |
| +import org.apache.lucene.luke.util.BytesRefUtils; |
| +import org.apache.lucene.luke.util.LoggerFactory; |
| +import org.apache.lucene.util.BytesRef; |
| + |
| +/** 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(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 liveDocs == null || liveDocs.get(docid); |
| + } |
| + |
| + @Override |
| + public List<DocumentField> getDocumentFields(int docid) { |
| + if (!isLive(docid)) { |
| + log.info("Doc #{} was deleted", docid); |
| + return Collections.emptyList(); |
| + } |
| + |
| + List<DocumentField> 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<Term> firstTerm(String field) { |
| + Objects.requireNonNull(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<Term> 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<Term> seekTerm(String termText) { |
| + Objects.requireNonNull(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<Integer> 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<Integer> 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<TermPosting> getTermPositions() { |
| + if (penum == null) { |
| + // postings enum is not initialized |
| + log.warn("Postings enum un-positioned for field: {}.", curField); |
| + return Collections.emptyList(); |
| + } |
| + |
| + List<TermPosting> 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<Integer> 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<TermVectorEntry> 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<DocValues> 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 00000000000..84d7af1b264 |
| --- /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 00000000000..643d299f1be |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermVectorEntry.java |
| @@ -0,0 +1,177 @@ |
| +/* |
| + * 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.ArrayList; |
| +import java.util.List; |
| +import java.util.Objects; |
| +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<TermVectorPosition> 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(TermsEnum te) throws IOException { |
| + Objects.requireNonNull(te); |
| + |
| + String termText = BytesRefUtils.decode(te.term()); |
| + |
| + List<TermVectorEntry.TermVectorPosition> 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<TermVectorPosition> 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<TermVectorPosition> 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, PostingsEnum pe) throws IOException { |
| + Objects.requireNonNull(pe); |
| + |
| + 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 00000000000..accdf253d4b |
| --- /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 java.io.IOException; |
| +import java.lang.invoke.MethodHandles; |
| +import java.util.ArrayList; |
| +import java.util.Collections; |
| +import java.util.List; |
| +import java.util.Objects; |
| + |
| +import org.apache.logging.log4j.Logger; |
| +import org.apache.lucene.index.IndexReader; |
| +import org.apache.lucene.index.Terms; |
| +import org.apache.lucene.index.TermsEnum; |
| +import org.apache.lucene.luke.util.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(IndexReader reader) { |
| + this.reader = Objects.requireNonNull(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<TermVectorEntry> 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<TermVectorEntry> 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 00000000000..6f4a38b753c |
| --- /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 00000000000..9913be368d2 |
| --- /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<Boolean> isOptimized(); |
| + |
| + /** |
| + * Returns the version number when this index was opened. |
| + * Empty Optional instance is returned if multiple indexes are opened. |
| + */ |
| + Optional<Long> 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<String> 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<String> getDirImpl(); |
| + |
| + /** |
| + * Returns the information of the commit point that reader has opened. |
| + * |
| + * Empty Optional instance is returned if multiple indexes are opened. |
| + */ |
| + Optional<String> 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<String> 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<String, Long> 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<TermStats> 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 00000000000..620e2e51501 |
| --- /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 00000000000..4dfd06be1e6 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/OverviewImpl.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.overview; |
| + |
| +import java.io.IOException; |
| +import java.util.List; |
| +import java.util.Locale; |
| +import java.util.Map; |
| +import java.util.Objects; |
| +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(IndexReader reader, String indexPath) { |
| + super(reader); |
| + this.indexPath = Objects.requireNonNull(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<Boolean> isOptimized() { |
| + if (commit != null) { |
| + return Optional.of(commit.getSegmentCount() == 1); |
| + } |
| + return Optional.empty(); |
| + } |
| + |
| + @Override |
| + public Optional<Long> getIndexVersion() { |
| + if (reader instanceof DirectoryReader) { |
| + return Optional.of(((DirectoryReader) reader).getVersion()); |
| + } |
| + return Optional.empty(); |
| + } |
| + |
| + @Override |
| + public Optional<String> 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<String> getDirImpl() { |
| + if (dir == null) { |
| + return Optional.empty(); |
| + } |
| + return Optional.of(dir.getClass().getName()); |
| + } |
| + |
| + @Override |
| + public Optional<String> getCommitDescription() { |
| + if (commit == null) { |
| + return Optional.empty(); |
| + } |
| + return Optional.of( |
| + commit.getSegmentsFileName() |
| + + " (generation=" + commit.getGeneration() |
| + + ", segs=" + commit.getSegmentCount() + ")"); |
| + } |
| + |
| + @Override |
| + public Optional<String> 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<String, Long> getSortedTermCounts(TermCountsOrder order) { |
| + if (order == null) { |
| + order = TermCountsOrder.COUNT_DESC; |
| + } |
| + return termCounts.sortedTermCounts(order); |
| + } |
| + |
| + @Override |
| + public List<TermStats> getTopTerms(String field, int numTerms) { |
| + Objects.requireNonNull(field); |
| + |
| + 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 00000000000..d48edd79d1f |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/overview/TermCounts.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.io.IOException; |
| +import java.util.Comparator; |
| +import java.util.LinkedHashMap; |
| +import java.util.Map; |
| +import java.util.Objects; |
| +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<String, Long> termCountMap; |
| + |
| + TermCounts(IndexReader reader) throws IOException { |
| + Objects.requireNonNull(reader); |
| + 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<String, Long> sortedTermCounts(TermCountsOrder order){ |
| + Objects.requireNonNull(order); |
| + |
| + Comparator<Map.Entry<String, Long>> comparator; |
| + switch (order) { |
| + case NAME_ASC: |
| + comparator = Map.Entry.comparingByKey(); |
| + break; |
| + case NAME_DESC: |
| + comparator = Map.Entry.<String, Long>comparingByKey().reversed(); |
| + break; |
| + case COUNT_ASC: |
| + comparator = Map.Entry.comparingByValue(); |
| + break; |
| + case COUNT_DESC: |
| + comparator = Map.Entry.<String, Long>comparingByValue().reversed(); |
| + break; |
| + default: |
| + comparator = Map.Entry.comparingByKey(); |
| + } |
| + return sortedTermCounts(comparator); |
| + } |
| + |
| + private Map<String, Long> sortedTermCounts(Comparator<Map.Entry<String, Long>> 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 00000000000..a5976ba8d52 |
| --- /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 00000000000..b97afe7c0ae |
| --- /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 00000000000..f1a00fe8d5c |
| --- /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 java.util.Arrays; |
| +import java.util.Collections; |
| +import java.util.List; |
| +import java.util.Map; |
| +import java.util.Objects; |
| +import java.util.WeakHashMap; |
| +import java.util.stream.Collectors; |
| + |
| +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<String, List<TermStats>> topTermsCache; |
| + |
| + TopTerms(IndexReader reader) { |
| + this.reader = Objects.requireNonNull(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<TermStats> 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<TermStats> topTerms = Arrays.stream(stats) |
| + .map(TermStats::of) |
| + .collect(Collectors.toList()); |
| + |
| + // cache computed statistics for later uses |
| + topTermsCache.put(field, topTerms); |
| + } |
| + |
| + return Collections.unmodifiableList(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 00000000000..11b12e81266 |
| --- /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 00000000000..0065130864b |
| --- /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 00000000000..f4d77061a56 |
| --- /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.Collections; |
| +import java.util.List; |
| + |
| +import org.apache.lucene.queries.mlt.MoreLikeThis; |
| + |
| +/** |
| + * Configurations for MoreLikeThis query. |
| + */ |
| +public final class MLTConfig { |
| + |
| + private final List<String> fields; |
| + |
| + private final int maxDocFreq; |
| + |
| + private final int minDocFreq; |
| + |
| + private final int minTermFreq; |
| + |
| + /** Builder for {@link MLTConfig} */ |
| + public static class Builder { |
| + |
| + private final List<String> 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<String> 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 = Collections.unmodifiableList(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 00000000000..4e7d984f648 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/search/QueryParserConfig.java |
| @@ -0,0 +1,252 @@ |
| +/* |
| + * 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.Collections; |
| +import java.util.HashMap; |
| +import java.util.Locale; |
| +import java.util.Map; |
| +import java.util.TimeZone; |
| +import java.util.stream.Collectors; |
| + |
| +import org.apache.lucene.document.DateTools; |
| + |
| +/** |
| + * Configurations for query parser. |
| + */ |
| +public final class QueryParserConfig { |
| + |
| + /** query operators */ |
| + 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<String, Class<? extends Number>> typeMap; |
| + |
| + /** Builder for {@link QueryParserConfig} */ |
| + 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<String, Class<? extends Number>> typeMap = new HashMap<>(); |
| + |
| + /** Builder for {@link QueryParserConfig} */ |
| + 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<String, Class<? extends Number>> 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 = Collections.unmodifiableMap(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<String, Class<? extends Number>> 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 00000000000..e8c41008a39 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/search/Search.java |
| @@ -0,0 +1,158 @@ |
| +/* |
| + * Licensed to the Apache Software Foundation (ASF) under one or more |
| + * contributor license agreements. See the NOTICE file distributed with |
| + * this work for additional information regarding copyright ownership. |
| + * The ASF licenses this file to You under the Apache License, Version 2.0 |
| + * (the "License"); you may not use this file except in compliance with |
| + * the License. You may obtain a copy of the License at |
| + * |
| + * http://www.apache.org/licenses/LICENSE-2.0 |
| + * |
| + * Unless required by applicable law or agreed to in writing, software |
| + * distributed under the License is distributed on an "AS IS" BASIS, |
| + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| + * See the License for the specific language governing permissions and |
| + * limitations under the License. |
| + */ |
| + |
| +package org.apache.lucene.luke.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<String> getFieldNames(); |
| + |
| + /** |
| + * Returns field names those are sortable. |
| + */ |
| + Collection<String> getSortableFieldNames(); |
| + |
| + /** |
| + * Returns field names those are searchable. |
| + */ |
| + Collection<String> getSearchableFieldNames(); |
| + |
| + /** |
| + * Returns field names those are searchable by range query. |
| + */ |
| + Collection<String> 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 |
| + * @param exactHitsCount - if set to true, the exact total hits count is returned. |
| + * @return search results |
| + * @throws LukeException - if an internal error occurs when accessing index |
| + */ |
| + SearchResults search(Query query, SimilarityConfig simConfig, Set<String> fieldsToLoad, int pageSize, boolean exactHitsCount); |
| + |
| + /** |
| + * 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 |
| + * @param exactHitsCount - if set to true, the exact total hits count is returned. |
| + * @return search results |
| + * @throws LukeException - if an internal error occurs when accessing index |
| + */ |
| + SearchResults search(Query query, SimilarityConfig simConfig, Sort sort, Set<String> fieldsToLoad, int pageSize, boolean exactHitsCount); |
| + |
| + /** |
| + * 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<SearchResults> 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<SearchResults> 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<SortField> 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<SortField> 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 00000000000..b2f97b11e6a |
| --- /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 00000000000..aa25a67288e |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchImpl.java |
| @@ -0,0 +1,471 @@ |
| +/* |
| + * 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.lang.invoke.MethodHandles; |
| +import java.text.NumberFormat; |
| +import java.util.Arrays; |
| +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.Objects; |
| +import java.util.Optional; |
| +import java.util.Set; |
| +import java.util.stream.Collectors; |
| + |
| +import org.apache.logging.log4j.Logger; |
| +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.luke.util.LoggerFactory; |
| +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.TopScoreDocCollector; |
| +import org.apache.lucene.search.TotalHits; |
| +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; |
| + |
| +/** 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 static final int DEFAULT_TOTAL_HITS_THRESHOLD = 1000; |
| + |
| + private final IndexSearcher searcher; |
| + |
| + private int pageSize = DEFAULT_PAGE_SIZE; |
| + |
| + private int currentPage = -1; |
| + |
| + private TotalHits totalHits; |
| + |
| + private ScoreDoc[] docs = new ScoreDoc[0]; |
| + |
| + private boolean exactHitsCount; |
| + |
| + private Query query; |
| + |
| + private Sort sort; |
| + |
| + private Set<String> fieldsToLoad; |
| + |
| + /** |
| + * Constructs a SearchImpl that holds given {@link IndexReader} |
| + * @param reader - the index reader |
| + */ |
| + public SearchImpl(IndexReader reader) { |
| + super(reader); |
| + this.searcher = new IndexSearcher(reader); |
| + } |
| + |
| + @Override |
| + public Collection<String> 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<String> 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<String> getRangeSearchableFieldNames() { |
| + return IndexUtils.getFieldNames(reader).stream() |
| + .map(f -> IndexUtils.getFieldInfo(reader, f)) |
| + .filter(info -> info.getPointDataDimensionCount() > 0) |
| + .map(info -> info.name) |
| + .collect(Collectors.toSet()); |
| + } |
| + |
| + @Override |
| + public Query getCurrentQuery() { |
| + return this.query; |
| + } |
| + |
| + @Override |
| + public Query parseQuery(String expression, String defField, Analyzer analyzer, |
| + QueryParserConfig config, boolean rewrite) { |
| + Objects.requireNonNull(expression); |
| + Objects.requireNonNull(defField); |
| + Objects.requireNonNull(analyzer); |
| + Objects.requireNonNull(config); |
| + |
| + 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(String expression, String defField, Analyzer analyzer, |
| + 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(String expression, String defField, Analyzer analyzer, |
| + 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<String, PointsConfig> pointsConfigMap = new HashMap<>(); |
| + |
| + for (Map.Entry<String, Class<? extends Number>> entry : config.getTypeMap().entrySet()) { |
| + String field = entry.getKey(); |
| + Class<? extends Number> 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( |
| + Query query, SimilarityConfig simConfig, Set<String> fieldsToLoad, int pageSize, boolean exactHitsCount) { |
| + return search(query, simConfig, null, fieldsToLoad, pageSize, exactHitsCount); |
| + } |
| + |
| + @Override |
| + public SearchResults search( |
| + Query query, SimilarityConfig simConfig, Sort sort, Set<String> fieldsToLoad, int pageSize, boolean exactHitsCount) { |
| + 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.exactHitsCount = exactHitsCount; |
| + this.query = Objects.requireNonNull(query); |
| + this.sort = sort; |
| + this.fieldsToLoad = fieldsToLoad == null ? null : Collections.unmodifiableSet(fieldsToLoad); |
| + searcher.setSimilarity(createSimilarity(Objects.requireNonNull(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; |
| + if (sort != null) { |
| + topDocs = searcher.searchAfter(after, query, pageSize, sort); |
| + } else { |
| + int hitsThreshold = exactHitsCount ? Integer.MAX_VALUE : DEFAULT_TOTAL_HITS_THRESHOLD; |
| + TopScoreDocCollector collector = TopScoreDocCollector.create(pageSize, after, hitsThreshold); |
| + searcher.search(query, collector); |
| + topDocs = collector.topDocs(); |
| + } |
| + |
| + // reset total hits for the current query |
| + this.totalHits = topDocs.totalHits; |
| + |
| + // 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, topDocs.scoreDocs, currentPage * pageSize, searcher, fieldsToLoad); |
| + } |
| + |
| + @Override |
| + public Optional<SearchResults> nextPage() { |
| + if (currentPage < 0 || query == null) { |
| + throw new LukeException(new IllegalStateException("Search session not started.")); |
| + } |
| + |
| + // proceed to next page |
| + currentPage += 1; |
| + |
| + if (totalHits.value == 0 || |
| + (totalHits.relation == TotalHits.Relation.EQUAL_TO && currentPage * pageSize >= totalHits.value)) { |
| + 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<SearchResults> 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(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<SortField> 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 Arrays.stream(new SortField[]{ |
| + new SortField(name, SortField.Type.INT), |
| + new SortField(name, SortField.Type.LONG), |
| + new SortField(name, SortField.Type.FLOAT), |
| + new SortField(name, SortField.Type.DOUBLE) |
| + }).collect(Collectors.toList()); |
| + |
| + case SORTED_NUMERIC: |
| + return Arrays.stream(new SortField[]{ |
| + new SortedNumericSortField(name, SortField.Type.INT), |
| + new SortedNumericSortField(name, SortField.Type.LONG), |
| + new SortedNumericSortField(name, SortField.Type.FLOAT), |
| + new SortedNumericSortField(name, SortField.Type.DOUBLE) |
| + }).collect(Collectors.toList()); |
| + |
| + case SORTED: |
| + return Arrays.stream(new SortField[] { |
| + new SortField(name, SortField.Type.STRING), |
| + new SortField(name, SortField.Type.STRING_VAL) |
| + }).collect(Collectors.toList()); |
| + |
| + case SORTED_SET: |
| + return Collections.singletonList(new SortedSetSortField(name, false)); |
| + |
| + default: |
| + return Collections.singletonList(new SortField(name, SortField.Type.DOC)); |
| + } |
| + |
| + } |
| + |
| + @Override |
| + public Optional<SortField> getSortType(String name, String type, boolean reverse) { |
| + Objects.requireNonNull(name); |
| + Objects.requireNonNull(type); |
| + List<SortField> 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 00000000000..7421e20b606 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchResults.java |
| @@ -0,0 +1,161 @@ |
| +/* |
| + * 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.util.ArrayList; |
| +import java.util.Collections; |
| +import java.util.HashMap; |
| +import java.util.List; |
| +import java.util.Map; |
| +import java.util.Objects; |
| +import java.util.Set; |
| +import java.util.stream.Collectors; |
| + |
| +import org.apache.lucene.document.Document; |
| +import org.apache.lucene.index.IndexableField; |
| +import org.apache.lucene.search.IndexSearcher; |
| +import org.apache.lucene.search.ScoreDoc; |
| +import org.apache.lucene.search.TotalHits; |
| + |
| +/** |
| + * Holder for a search result page. |
| + */ |
| +public final class SearchResults { |
| + |
| + private TotalHits totalHits; |
| + |
| + private int offset = 0; |
| + |
| + private List<Doc> 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(TotalHits totalHits, ScoreDoc[] docs, int offset, |
| + IndexSearcher searcher, Set<String> fieldsToLoad) |
| + throws IOException { |
| + SearchResults res = new SearchResults(); |
| + |
| + res.totalHits = Objects.requireNonNull(totalHits); |
| + Objects.requireNonNull(docs); |
| + Objects.requireNonNull(searcher); |
| + |
| + 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 TotalHits getTotalHits() { |
| + return totalHits; |
| + } |
| + |
| + /** |
| + * Returns the offset of the current page. |
| + */ |
| + public int getOffset() { |
| + return offset; |
| + } |
| + |
| + /** |
| + * Returns the documents of the current page. |
| + */ |
| + public List<Doc> getHits() { |
| + return Collections.unmodifiableList(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<String, String[]> 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, Document luceneDoc) { |
| + Objects.requireNonNull(luceneDoc); |
| + |
| + Doc doc = new Doc(); |
| + doc.docId = docId; |
| + doc.score = score; |
| + Set<String> 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<String, String[]> getFieldValues() { |
| + return Collections.unmodifiableMap(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 00000000000..072d1c54351 |
| --- /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; |
| + |
| + /** Builder for {@link SimilarityConfig} */ |
| + 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 00000000000..63433a1bf2c |
| --- /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 00000000000..877646cd4b4 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexTools.java |
| @@ -0,0 +1,97 @@ |
| +/* |
| + * 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 org.apache.lucene.analysis.Analyzer; |
| +import org.apache.lucene.document.Document; |
| +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. |
| + * |
| + * <p> |
| + * Merges are executed until there are <i>maxNumSegments</i> segments. <br> |
| + * When <i>expunge</i> is true, <i>maxNumSegments</i> parameter is ignored. |
| + * </p> |
| + * |
| + * @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. |
| + * |
| + * <p>This method must be called with the return value from {@link IndexTools#checkIndex(PrintStream)}.</p> |
| + * |
| + * @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); |
| + |
| + /** |
| + * Create a new index. |
| + * |
| + * @throws LukeException - if an internal error occurs when accessing index |
| + */ |
| + void createNewIndex(); |
| + |
| + /** |
| + * Create a new index with sample documents. |
| + * @param dataDir - the directory path which contains sample documents (20 Newsgroups). |
| + */ |
| + void createNewIndex(String dataDir); |
| +} |
| 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 00000000000..c3bd86376a1 |
| --- /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 00000000000..166958b8d10 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/tools/IndexToolsImpl.java |
| @@ -0,0 +1,187 @@ |
| +/* |
| + * 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.IOException; |
| +import java.io.PrintStream; |
| +import java.nio.file.Path; |
| +import java.nio.file.Paths; |
| +import java.util.List; |
| +import java.util.Objects; |
| + |
| +import org.apache.lucene.analysis.Analyzer; |
| +import org.apache.lucene.document.Document; |
| +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.luke.models.util.twentynewsgroups.Message; |
| +import org.apache.lucene.luke.models.util.twentynewsgroups.MessageFilesParser; |
| +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; |
| + |
| + /** |
| + * 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(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(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, Analyzer analyzer) { |
| + Objects.requireNonNull(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(Query query) { |
| + Objects.requireNonNull(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 |
| + public void createNewIndex() { |
| + createNewIndex(null); |
| + } |
| + |
| + @Override |
| + public void createNewIndex(String dataDir) { |
| + IndexWriter writer = null; |
| + try { |
| + if (dir == null || dir.listAll().length > 0) { |
| + // Directory is null or not empty |
| + throw new IllegalStateException(); |
| + } |
| + |
| + writer = IndexUtils.createWriter(dir, Message.createLuceneAnalyzer(), useCompound, keepAllCommits); |
| + |
| + if (Objects.nonNull(dataDir)) { |
| + Path path = Paths.get(dataDir); |
| + MessageFilesParser parser = new MessageFilesParser(path); |
| + List<Message> messages = parser.parseAll(); |
| + for (Message message : messages) { |
| + writer.addDocument(message.toLuceneDoc()); |
| + } |
| + } |
| + |
| + writer.commit(); |
| + } catch (IOException e) { |
| + throw new LukeException("Cannot create new index.", e); |
| + } finally { |
| + if (writer != null) { |
| + try { |
| + writer.close(); |
| + } catch (IOException e) {} |
| + } |
| + } |
| + } |
| +} |
| 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 00000000000..cb76b17725e |
| --- /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 00000000000..e59689a4c29 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/util/IndexUtils.java |
| @@ -0,0 +1,497 @@ |
| +/* |
| + * 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 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.Objects; |
| +import java.util.stream.Collectors; |
| +import java.util.stream.StreamSupport; |
| + |
| +import org.apache.logging.log4j.Logger; |
| +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.luke.util.LoggerFactory; |
| +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; |
| + |
| +/** |
| + * Utilities for various raw index operations. |
| + * |
| + * <p> |
| + * This is for internal uses, DO NOT call from UI components or applications. |
| + * </p> |
| + */ |
| +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 Exception - if there is a low level IO error. |
| + */ |
| + public static IndexReader openIndex(String indexPath, String dirImpl) |
| + throws Exception { |
| + final Path root = FileSystems.getDefault().getPath(Objects.requireNonNull(indexPath)); |
| + final List<DirectoryReader> readers = new ArrayList<>(); |
| + |
| + // find all valid index directories in this directory |
| + Files.walkFileTree(root, new SimpleFileVisitor<Path>() { |
| + @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[readers.size()])); |
| + } |
| + } |
| + |
| + /** |
| + * Opens an index directory for given index path. |
| + * |
| + * <p>This can be used to open/repair corrupted indexes.</p> |
| + * |
| + * @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(String dirPath, String dirImpl) throws IOException { |
| + final Path path = FileSystems.getDefault().getPath(Objects.requireNonNull(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(Path path, String dirImpl) throws IOException { |
| + if (!Files.exists(Objects.requireNonNull(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(Directory dir, Analyzer analyzer, boolean useCompound, boolean keepAllCommits) throws IOException { |
| + return createWriter(Objects.requireNonNull(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(Directory dir, Analyzer analyzer, boolean useCompound, boolean keepAllCommits, |
| + PrintStream ps) throws IOException { |
| + Objects.requireNonNull(dir); |
| + |
| + 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(IndexWriter writer, boolean expunge, int maxNumSegments) throws IOException { |
| + Objects.requireNonNull(writer); |
| + 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(Directory dir, PrintStream ps) throws IOException { |
| + Objects.requireNonNull(dir); |
| + |
| + 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(Directory dir, CheckIndex.Status st, PrintStream ps) throws IOException { |
| + Objects.requireNonNull(dir); |
| + Objects.requireNonNull(st); |
| + |
| + 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(Directory dir) throws IOException { |
| + Objects.requireNonNull(dir); |
| + |
| + return new SegmentInfos.FindSegmentsFile<String>(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(IndexCommit ic) throws IOException { |
| + Map<String, String> userDataMap = Objects.requireNonNull(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<String, Long> countTerms(IndexReader reader, Collection<String> fields) throws IOException { |
| + Map<String, Long> 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 the {@link Bits} representing live documents in the index. |
| + * |
| + * @param reader - index reader |
| + */ |
| + public static Bits getLiveDocs(IndexReader reader) { |
| + if (reader instanceof LeafReader) { |
| + return ((LeafReader) reader).getLiveDocs(); |
| + } else { |
| + return MultiBits.getLiveDocs(reader); |
| + } |
| + } |
| + |
| + /** |
| + * 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<String> 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 00000000000..29354bd9273 |
| --- /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/models/util/twentynewsgroups/Message.java b/lucene/luke/src/java/org/apache/lucene/luke/models/util/twentynewsgroups/Message.java |
| new file mode 100644 |
| index 00000000000..e62d2c052d4 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/util/twentynewsgroups/Message.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.models.util.twentynewsgroups; |
| + |
| +import java.util.HashMap; |
| +import java.util.Map; |
| +import java.util.Objects; |
| + |
| +import org.apache.lucene.analysis.Analyzer; |
| +import org.apache.lucene.analysis.miscellaneous.PerFieldAnalyzerWrapper; |
| +import org.apache.lucene.analysis.standard.StandardAnalyzer; |
| +import org.apache.lucene.analysis.standard.UAX29URLEmailAnalyzer; |
| +import org.apache.lucene.document.Document; |
| +import org.apache.lucene.document.Field; |
| +import org.apache.lucene.document.FieldType; |
| +import org.apache.lucene.document.IntPoint; |
| +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.IndexOptions; |
| +import org.apache.lucene.util.BytesRef; |
| + |
| +/** Data holder class for a newsgroups message */ |
| +public class Message { |
| + |
| + private String from; |
| + private String[] newsgroups; |
| + private String subject; |
| + private String messageId; |
| + private String date; |
| + private String organization; |
| + private String body; |
| + private int lines; |
| + |
| + public String getFrom() { |
| + return from; |
| + } |
| + |
| + public void setFrom(String from) { |
| + this.from = from; |
| + } |
| + |
| + public String[] getNewsgroups() { |
| + return newsgroups; |
| + } |
| + |
| + public void setNewsgroups(String[] newsgroups) { |
| + this.newsgroups = newsgroups; |
| + } |
| + |
| + public String getSubject() { |
| + return subject; |
| + } |
| + |
| + public void setSubject(String subject) { |
| + this.subject = subject; |
| + } |
| + |
| + public String getMessageId() { |
| + return messageId; |
| + } |
| + |
| + public void setMessageId(String messageId) { |
| + this.messageId = messageId; |
| + } |
| + |
| + public String getDate() { |
| + return date; |
| + } |
| + |
| + public void setDate(String date) { |
| + this.date = date; |
| + } |
| + |
| + public String getOrganization() { |
| + return organization; |
| + } |
| + |
| + public void setOrganization(String organization) { |
| + this.organization = organization; |
| + } |
| + |
| + public String getBody() { |
| + return body; |
| + } |
| + |
| + public void setBody(String body) { |
| + this.body = body; |
| + } |
| + |
| + public int getLines() { |
| + return lines; |
| + } |
| + |
| + public void setLines(int lines) { |
| + this.lines = lines; |
| + } |
| + |
| + public Document toLuceneDoc() { |
| + Document doc = new Document(); |
| + |
| + if (Objects.nonNull(getFrom())) { |
| + doc.add(new TextField("from", getFrom(), Field.Store.YES)); |
| + } |
| + |
| + if (Objects.nonNull(getNewsgroups())) { |
| + for (String newsgroup : getNewsgroups()) { |
| + doc.add(new StringField("newsgroup", newsgroup, Field.Store.YES)); |
| + doc.add(new SortedSetDocValuesField("newsgroup_sort", new BytesRef(newsgroup))); |
| + } |
| + } |
| + |
| + if (Objects.nonNull(getSubject())) { |
| + doc.add(new Field("subject", getSubject(), SUBJECT_FIELD_TYPE)); |
| + } |
| + |
| + if (Objects.nonNull(getMessageId())) { |
| + doc.add(new StringField("messageId", getMessageId(), Field.Store.YES)); |
| + } |
| + |
| + if (Objects.nonNull(getDate())) { |
| + doc.add(new StoredField("date_raw", getDate())); |
| + } |
| + |
| + |
| + if (getOrganization() != null) { |
| + doc.add(new TextField("organization", getOrganization(), Field.Store.YES)); |
| + } |
| + |
| + doc.add(new IntPoint("lines_range", getLines())); |
| + doc.add(new SortedNumericDocValuesField("lines_sort", getLines())); |
| + doc.add(new StoredField("lines_raw", String.valueOf(getLines()))); |
| + |
| + if (Objects.nonNull(getBody())) { |
| + doc.add(new Field("body", getBody(), BODY_FIELD_TYPE)); |
| + } |
| + |
| + return doc; |
| + } |
| + |
| + public static Analyzer createLuceneAnalyzer() { |
| + Map<String, Analyzer> map = new HashMap<>(); |
| + map.put("from", new UAX29URLEmailAnalyzer()); |
| + return new PerFieldAnalyzerWrapper(new StandardAnalyzer(), map); |
| + } |
| + |
| + private final static FieldType SUBJECT_FIELD_TYPE; |
| + |
| + private final static FieldType BODY_FIELD_TYPE; |
| + |
| + static { |
| + SUBJECT_FIELD_TYPE = new FieldType(); |
| + SUBJECT_FIELD_TYPE.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS); |
| + SUBJECT_FIELD_TYPE.setTokenized(true); |
| + SUBJECT_FIELD_TYPE.setStored(true); |
| + |
| + BODY_FIELD_TYPE = new FieldType(); |
| + BODY_FIELD_TYPE.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS); |
| + BODY_FIELD_TYPE.setTokenized(true); |
| + BODY_FIELD_TYPE.setStored(true); |
| + BODY_FIELD_TYPE.setStoreTermVectors(true); |
| + BODY_FIELD_TYPE.setStoreTermVectorPositions(true); |
| + BODY_FIELD_TYPE.setStoreTermVectorOffsets(true); |
| + } |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/util/twentynewsgroups/MessageFilesParser.java b/lucene/luke/src/java/org/apache/lucene/luke/models/util/twentynewsgroups/MessageFilesParser.java |
| new file mode 100644 |
| index 00000000000..5a2fe739849 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/util/twentynewsgroups/MessageFilesParser.java |
| @@ -0,0 +1,123 @@ |
| +/* |
| + * 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.twentynewsgroups; |
| + |
| +import java.io.BufferedReader; |
| +import java.io.IOException; |
| +import java.lang.invoke.MethodHandles; |
| +import java.nio.charset.StandardCharsets; |
| +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.List; |
| + |
| +import org.apache.logging.log4j.Logger; |
| +import org.apache.lucene.luke.util.LoggerFactory; |
| + |
| +/** 20 Newsgroups (http://kdd.ics.uci.edu/databases/20newsgroups/20newsgroups.html) message files parser */ |
| +public class MessageFilesParser extends SimpleFileVisitor<Path> { |
| + |
| + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| + |
| + private final Path root; |
| + |
| + private final List<Message> messages = new ArrayList<>(); |
| + |
| + public MessageFilesParser(Path root) { |
| + this.root = root; |
| + } |
| + |
| + public FileVisitResult visitFile(Path file, BasicFileAttributes attr) { |
| + try { |
| + if (attr.isRegularFile()) { |
| + Message message = parse(file); |
| + if (message != null) { |
| + messages.add(parse(file)); |
| + } |
| + } |
| + } catch (IOException e) { |
| + log.warn("Invalid file? " + file.toString()); |
| + } |
| + return FileVisitResult.CONTINUE; |
| + } |
| + |
| + Message parse(Path file) throws IOException { |
| + try (BufferedReader br = Files.newBufferedReader(file, StandardCharsets.UTF_8)) { |
| + String line = br.readLine(); |
| + |
| + Message message = new Message(); |
| + while (!line.equals("")) { |
| + String[] ary = line.split(":", 2); |
| + if (ary.length < 2) { |
| + line = br.readLine(); |
| + continue; |
| + } |
| + String att = ary[0].trim(); |
| + String val = ary[1].trim(); |
| + switch (att) { |
| + case "From": |
| + message.setFrom(val); |
| + break; |
| + case "Newsgroups": |
| + message.setNewsgroups(val.split(",")); |
| + break; |
| + case "Subject": |
| + message.setSubject(val); |
| + break; |
| + case "Message-ID": |
| + message.setMessageId(val); |
| + break; |
| + case "Date": |
| + message.setDate(val); |
| + break; |
| + case "Organization": |
| + message.setOrganization(val); |
| + break; |
| + case "Lines": |
| + try { |
| + message.setLines(Integer.parseInt(ary[1].trim())); |
| + } catch (NumberFormatException e) {} |
| + break; |
| + default: |
| + break; |
| + } |
| + |
| + line = br.readLine(); |
| + } |
| + |
| + StringBuilder sb = new StringBuilder(); |
| + while (line != null) { |
| + sb.append(line); |
| + sb.append(" "); |
| + line = br.readLine(); |
| + } |
| + message.setBody(sb.toString()); |
| + |
| + return message; |
| + } |
| + } |
| + |
| + public List<Message> parseAll() throws IOException { |
| + Files.walkFileTree(root, this); |
| + return messages; |
| + } |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/util/twentynewsgroups/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/models/util/twentynewsgroups/package-info.java |
| new file mode 100644 |
| index 00000000000..58218fb3ea3 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/models/util/twentynewsgroups/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 indexing 20 Newsgroups data */ |
| +package org.apache.lucene.luke.models.util.twentynewsgroups; |
| \ 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 00000000000..9c6a51e1c8b |
| --- /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 00000000000..4c7cf18657f |
| --- /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/LoggerFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/util/LoggerFactory.java |
| new file mode 100644 |
| index 00000000000..4735d64ad56 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/util/LoggerFactory.java |
| @@ -0,0 +1,73 @@ |
| +/* |
| + * 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 java.nio.charset.StandardCharsets; |
| + |
| +import org.apache.logging.log4j.Level; |
| +import org.apache.logging.log4j.LogManager; |
| +import org.apache.logging.log4j.Logger; |
| +import org.apache.logging.log4j.core.Appender; |
| +import org.apache.logging.log4j.core.LoggerContext; |
| +import org.apache.logging.log4j.core.appender.FileAppender; |
| +import org.apache.logging.log4j.core.config.Configurator; |
| +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; |
| +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; |
| +import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; |
| +import org.apache.logging.log4j.core.layout.PatternLayout; |
| +import org.apache.lucene.luke.app.desktop.util.TextAreaAppender; |
| + |
| +/** |
| + * Logger factory. This programmatically configurates logger context (Appenders etc.) |
| + */ |
| +public class LoggerFactory { |
| + |
| + public static void initGuiLogging(String logFile) { |
| + ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder(); |
| + builder.add(builder.newRootLogger(Level.INFO)); |
| + LoggerContext context = Configurator.initialize(builder.build()); |
| + |
| + PatternLayout layout = PatternLayout.newBuilder() |
| + .withPattern("[%d{ISO8601}] %5p (%F:%L) - %m%n") |
| + .withCharset(StandardCharsets.UTF_8) |
| + .build(); |
| + |
| + Appender fileAppender = FileAppender.newBuilder() |
| + .setName("File") |
| + .setLayout(layout) |
| + .withFileName(logFile) |
| + .withAppend(false) |
| + .build(); |
| + fileAppender.start(); |
| + |
| + Appender textAreaAppender = TextAreaAppender.newBuilder() |
| + .setName("TextArea") |
| + .setLayout(layout) |
| + .build(); |
| + textAreaAppender.start(); |
| + |
| + context.getRootLogger().addAppender(fileAppender); |
| + context.getRootLogger().addAppender(textAreaAppender); |
| + context.updateLoggers(); |
| + } |
| + |
| + public static Logger getLogger(Class<?> clazz) { |
| + return LogManager.getLogger(clazz); |
| + } |
| + |
| +} |
| 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 00000000000..e9830cf28e6 |
| --- /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/org/apache/lucene/luke/util/reflection/ClassScanner.java b/lucene/luke/src/java/org/apache/lucene/luke/util/reflection/ClassScanner.java |
| new file mode 100644 |
| index 00000000000..2937298aee2 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/util/reflection/ClassScanner.java |
| @@ -0,0 +1,113 @@ |
| +/* |
| + * 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.reflection; |
| + |
| +import java.io.IOException; |
| +import java.lang.invoke.MethodHandles; |
| +import java.net.URL; |
| +import java.util.ArrayList; |
| +import java.util.Collections; |
| +import java.util.Enumeration; |
| +import java.util.HashSet; |
| +import java.util.List; |
| +import java.util.Set; |
| +import java.util.concurrent.ExecutorService; |
| +import java.util.concurrent.Executors; |
| +import java.util.concurrent.TimeUnit; |
| + |
| +import org.apache.logging.log4j.Logger; |
| +import org.apache.lucene.luke.util.LoggerFactory; |
| +import org.apache.lucene.util.NamedThreadFactory; |
| + |
| +/** |
| + * Utility class for scanning class files in jars. |
| + */ |
| +public class ClassScanner { |
| + |
| + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| + |
| + private final String packageName; |
| + private final ClassLoader[] classLoaders; |
| + |
| + public ClassScanner(String packageName, ClassLoader... classLoaders) { |
| + this.packageName = packageName; |
| + this.classLoaders = classLoaders; |
| + } |
| + |
| + public <T> Set<Class<? extends T>> scanSubTypes(Class<T> superType) { |
| + final int numThreads = Runtime.getRuntime().availableProcessors(); |
| + |
| + List<SubtypeCollector<T>> collectors = new ArrayList<>(); |
| + for (int i = 0; i < numThreads; i++) { |
| + collectors.add(new SubtypeCollector<T>(superType, packageName, classLoaders)); |
| + } |
| + |
| + try { |
| + List<URL> urls = getJarUrls(); |
| + for (int i = 0; i < urls.size(); i++) { |
| + collectors.get(i % numThreads).addUrl(urls.get(i)); |
| + } |
| + |
| + ExecutorService executorService = Executors.newFixedThreadPool(numThreads, new NamedThreadFactory("scanner-scan-subtypes")); |
| + for (SubtypeCollector<T> collector : collectors) { |
| + executorService.submit(collector); |
| + } |
| + |
| + try { |
| + executorService.shutdown(); |
| + executorService.awaitTermination(10, TimeUnit.SECONDS); |
| + } catch (InterruptedException e) { |
| + } finally { |
| + executorService.shutdownNow(); |
| + } |
| + |
| + Set<Class<? extends T>> types = new HashSet<>(); |
| + for (SubtypeCollector<T> collector : collectors) { |
| + types.addAll(collector.getTypes()); |
| + } |
| + return types; |
| + } catch (IOException e) { |
| + log.error("Cannot load jar file entries", e); |
| + } |
| + return Collections.emptySet(); |
| + } |
| + |
| + private List<URL> getJarUrls() throws IOException { |
| + List<URL> urls = new ArrayList<>(); |
| + String resourceName = resourceName(packageName); |
| + for (ClassLoader loader : classLoaders) { |
| + for (Enumeration<URL> e = loader.getResources(resourceName); e.hasMoreElements(); ) { |
| + URL url = e.nextElement(); |
| + // extract jar file path from the resource name |
| + int index = url.getPath().lastIndexOf(".jar"); |
| + if (index > 0) { |
| + String path = url.getPath().substring(0, index + 4); |
| + urls.add(new URL(path)); |
| + } |
| + } |
| + } |
| + return urls; |
| + } |
| + |
| + private static String resourceName(String packageName) { |
| + if (packageName == null || packageName.equals("")) { |
| + return packageName; |
| + } |
| + return packageName.replace('.', '/'); |
| + } |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/util/reflection/SubtypeCollector.java b/lucene/luke/src/java/org/apache/lucene/luke/util/reflection/SubtypeCollector.java |
| new file mode 100644 |
| index 00000000000..f10d1316615 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/util/reflection/SubtypeCollector.java |
| @@ -0,0 +1,101 @@ |
| +/* |
| + * 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.reflection; |
| + |
| +import java.io.IOException; |
| +import java.lang.invoke.MethodHandles; |
| +import java.net.URL; |
| +import java.util.Collections; |
| +import java.util.HashSet; |
| +import java.util.Objects; |
| +import java.util.Set; |
| +import java.util.jar.JarInputStream; |
| +import java.util.zip.ZipEntry; |
| + |
| +import org.apache.logging.log4j.Logger; |
| +import org.apache.lucene.luke.util.LoggerFactory; |
| + |
| +final class SubtypeCollector<T> implements Runnable { |
| + |
| + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| + |
| + private final Set<URL> urls = new HashSet<>(); |
| + |
| + private final Class<T> superType; |
| + |
| + private final String packageName; |
| + |
| + private final ClassLoader[] classLoaders; |
| + |
| + private final Set<Class<? extends T>> types = new HashSet<>(); |
| + |
| + SubtypeCollector(Class<T> superType, String packageName, ClassLoader... classLoaders) { |
| + this.superType = superType; |
| + this.packageName = packageName; |
| + this.classLoaders = classLoaders; |
| + } |
| + |
| + void addUrl(URL url) { |
| + urls.add(url); |
| + } |
| + |
| + Set<Class<? extends T>> getTypes() { |
| + return Collections.unmodifiableSet(types); |
| + } |
| + |
| + @Override |
| + public void run() { |
| + for (URL url : urls) { |
| + try (JarInputStream jis = new JarInputStream(url.openStream())) { |
| + // iterate all zip entry in the jar |
| + ZipEntry entry; |
| + while ((entry = jis.getNextEntry()) != null) { |
| + String name = entry.getName(); |
| + if (name.endsWith(".class") && name.indexOf('$') < 0 |
| + && !name.contains("package-info") && !name.startsWith("META-INF")) { |
| + String fqcn = convertToFQCN(name); |
| + if (!fqcn.startsWith(packageName)) { |
| + continue; |
| + } |
| + for (ClassLoader cl : classLoaders) { |
| + try { |
| + Class<?> clazz = Class.forName(fqcn, false, cl); |
| + if (superType.isAssignableFrom(clazz) && !Objects.equals(superType, clazz)) { |
| + types.add(clazz.asSubclass(superType)); |
| + } |
| + break; |
| + } catch (Throwable e) { |
| + } |
| + } |
| + } |
| + } |
| + } catch (IOException e) { |
| + log.error("Cannot load jar " + url.toString(), e); |
| + } |
| + } |
| + } |
| + |
| + private static String convertToFQCN(String name) { |
| + if (name == null || name.equals("")) { |
| + return name; |
| + } |
| + int index = name.lastIndexOf(".class"); |
| + return name.replace('/', '.').substring(0, index); |
| + } |
| + |
| +} |
| diff --git a/lucene/luke/src/java/org/apache/lucene/luke/util/reflection/package-info.java b/lucene/luke/src/java/org/apache/lucene/luke/util/reflection/package-info.java |
| new file mode 100644 |
| index 00000000000..268245e2ad7 |
| --- /dev/null |
| +++ b/lucene/luke/src/java/org/apache/lucene/luke/util/reflection/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 reflections */ |
| +package org.apache.lucene.luke.util.reflection; |
| \ 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 00000000000..534560c124e |
| --- /dev/null |
| +++ b/lucene/luke/src/java/overview.html |
| @@ -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. |
| + --> |
| + |
| +<html> |
| +<head> |
| + <meta charset="UTF-8"> |
| + <title>Luke</title> |
| +</head> |
| +<body> |
| +Luke - Lucene Toolbox |
| +</body> |
| +</html> |
| \ No newline at end of file |
| diff --git a/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/font/ElegantIcons.ttf b/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/font/ElegantIcons.ttf |
| new file mode 100644 |
| index 00000000000..12ff680025e |
| Binary files /dev/null and b/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/font/ElegantIcons.ttf differ |
| diff --git a/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/img/indicator.gif b/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/img/indicator.gif |
| new file mode 100644 |
| index 00000000000..d0bce154234 |
| Binary files /dev/null and b/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/img/indicator.gif differ |
| diff --git a/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/img/lucene-logo.gif b/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/img/lucene-logo.gif |
| new file mode 100755 |
| index 00000000000..0317bbb1608 |
| Binary files /dev/null and b/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/img/lucene-logo.gif differ |
| diff --git a/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/img/lucene.gif b/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/img/lucene.gif |
| new file mode 100755 |
| index 00000000000..b4eeddb3c38 |
| Binary files /dev/null and b/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/img/lucene.gif differ |
| diff --git a/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/img/luke-logo.gif b/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/img/luke-logo.gif |
| new file mode 100755 |
| index 00000000000..4ec2fff11d8 |
| Binary files /dev/null and b/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/img/luke-logo.gif differ |
| diff --git a/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/messages/messages.properties b/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/messages/messages.properties |
| new file mode 100644 |
| index 00000000000..94fe4063140 |
| --- /dev/null |
| +++ b/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/messages/messages.properties |
| @@ -0,0 +1,280 @@ |
| +# |
| +# 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 |
| +button.create=Create |
| +button.clear=Clear |
| +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.create_index=Create new 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. |
| +# Create index |
| +createindex.dialog.title=Choose new index directory path |
| +createindex.label.location=Location: |
| +createindex.label.dirname=Index directory name: |
| +createindex.label.option=(Options) |
| +createindex.label.data_link=http://kdd.ics.uci.edu/databases/20newsgroups/20newsgroups.html |
| +createindex.label.datadir=Data directory: |
| +createindex.textarea.data_help1=You can index sample documents from 20 Newsgroups corpus that is available at here: |
| +createindex.textarea.data_help2=Download and extract the tgz file, then select the extracted directory path.\nCreating an index with the full size corpus takes some time... :) |
| +# 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=<html><p>Hint: <br> Edit the text field above and press Enter to seek to <br> arbitrary terms.<p></html> |
| +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.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.checkbox.exact_hits_cnt=exact hits count |
| +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_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/org/apache/lucene/luke/app/desktop/util/inifile/SimpleIniFileTest.java b/lucene/luke/src/test/org/apache/lucene/luke/app/desktop/util/inifile/SimpleIniFileTest.java |
| new file mode 100644 |
| index 00000000000..c345800e53e |
| --- /dev/null |
| +++ b/lucene/luke/src/test/org/apache/lucene/luke/app/desktop/util/inifile/SimpleIniFileTest.java |
| @@ -0,0 +1,115 @@ |
| +/* |
| + * 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.inifile; |
| + |
| +import java.io.BufferedReader; |
| +import java.io.IOException; |
| +import java.nio.charset.StandardCharsets; |
| +import java.nio.file.Files; |
| +import java.nio.file.Path; |
| +import java.util.List; |
| +import java.util.Map; |
| +import java.util.stream.Collectors; |
| + |
| +import org.apache.lucene.util.LuceneTestCase; |
| +import org.junit.Test; |
| + |
| +public class SimpleIniFileTest extends LuceneTestCase { |
| + |
| + @Test |
| + public void testStore() throws IOException { |
| + Path path = saveTestIni(); |
| + assertTrue(Files.exists(path)); |
| + assertTrue(Files.isRegularFile(path)); |
| + |
| + try (BufferedReader br = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { |
| + List<String> lines = br.lines().collect(Collectors.toList()); |
| + assertEquals(8, lines.size()); |
| + assertEquals("[section1]", lines.get(0)); |
| + assertEquals("s1 = aaa", lines.get(1)); |
| + assertEquals("s2 = bbb", lines.get(2)); |
| + assertEquals("", lines.get(3)); |
| + assertEquals("[section2]", lines.get(4)); |
| + assertEquals("b1 = true", lines.get(5)); |
| + assertEquals("b2 = false", lines.get(6)); |
| + assertEquals("", lines.get(7)); |
| + } |
| + } |
| + |
| + @Test |
| + public void testLoad() throws IOException { |
| + Path path = saveTestIni(); |
| + |
| + SimpleIniFile iniFile = new SimpleIniFile(); |
| + iniFile.load(path); |
| + |
| + Map<String, OptionMap> sections = iniFile.getSections(); |
| + assertEquals(2, sections.size()); |
| + assertEquals(2, sections.get("section1").size()); |
| + assertEquals(2, sections.get("section2").size()); |
| + } |
| + |
| + @Test |
| + public void testPut() { |
| + SimpleIniFile iniFile = new SimpleIniFile(); |
| + iniFile.put("section1", "s1", "aaa"); |
| + iniFile.put("section1", "s1", "aaa_updated"); |
| + iniFile.put("section2", "b1", true); |
| + iniFile.put("section2", "b2", null); |
| + |
| + Map<String, OptionMap> sections = iniFile.getSections(); |
| + assertEquals("aaa_updated", sections.get("section1").get("s1")); |
| + assertEquals("true", sections.get("section2").get("b1")); |
| + assertNull(sections.get("section2").get("b2")); |
| + } |
| + |
| + @Test |
| + public void testGet() throws IOException { |
| + Path path = saveTestIni(); |
| + SimpleIniFile iniFile = new SimpleIniFile(); |
| + iniFile.load(path); |
| + |
| + assertNull(iniFile.getString("", "")); |
| + |
| + assertEquals("aaa", iniFile.getString("section1", "s1")); |
| + assertEquals("bbb", iniFile.getString("section1", "s2")); |
| + assertNull(iniFile.getString("section1", "s3")); |
| + assertNull(iniFile.getString("section1", "")); |
| + |
| + assertEquals(true, iniFile.getBoolean("section2", "b1")); |
| + assertEquals(false, iniFile.getBoolean("section2", "b2")); |
| + assertFalse(iniFile.getBoolean("section2", "b3")); |
| + } |
| + |
| + private Path saveTestIni() throws IOException { |
| + SimpleIniFile iniFile = new SimpleIniFile(); |
| + iniFile.put("", "s0", "000"); |
| + |
| + iniFile.put("section1", "s1", "aaa"); |
| + iniFile.put("section1", "s2", "---"); |
| + iniFile.put("section1", "s2", "bbb"); |
| + iniFile.put("section1", "", "ccc"); |
| + |
| + iniFile.put("section2", "b1", true); |
| + iniFile.put("section2", "b2", false); |
| + |
| + Path path = createTempFile(); |
| + iniFile.store(path); |
| + return path; |
| + } |
| +} |
| 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 00000000000..39e8eca1e78 |
| --- /dev/null |
| +++ b/lucene/luke/src/test/org/apache/lucene/luke/models/analysis/AnalysisImplTest.java |
| @@ -0,0 +1,136 @@ |
| +/* |
| + * 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.HashMap; |
| +import java.util.List; |
| +import java.util.Map; |
| + |
| +import org.apache.lucene.analysis.Analyzer; |
| +import org.apache.lucene.analysis.custom.CustomAnalyzer; |
| +import org.apache.lucene.luke.models.LukeException; |
| +import org.apache.lucene.util.LuceneTestCase; |
| +import org.junit.Test; |
| + |
| +public class AnalysisImplTest extends LuceneTestCase { |
| + |
| + @Test |
| + public void testGetPresetAnalyzerTypes() throws Exception { |
| + AnalysisImpl analysis = new AnalysisImpl(); |
| + Collection<Class<? extends Analyzer>> analyerTypes = analysis.getPresetAnalyzerTypes(); |
| + assertNotNull(analyerTypes); |
| + for (Class<? extends Analyzer> clazz : analyerTypes) { |
| + clazz.newInstance(); |
| + } |
| + } |
| + |
| + @Test |
| + public void testGetAvailableCharFilters() { |
| + AnalysisImpl analysis = new AnalysisImpl(); |
| + Collection<String> charFilters = analysis.getAvailableCharFilters(); |
| + assertNotNull(charFilters); |
| + } |
| + |
| + @Test |
| + public void testGetAvailableTokenizers() { |
| + AnalysisImpl analysis = new AnalysisImpl(); |
| + Collection<String> tokenizers = analysis.getAvailableTokenizers(); |
| + assertNotNull(tokenizers); |
| + } |
| + |
| + @Test |
| + public void testGetAvailableTokenFilters() { |
| + AnalysisImpl analysis = new AnalysisImpl(); |
| + Collection<String> tokenFilters = analysis.getAvailableTokenFilters(); |
| + assertNotNull(tokenFilters); |
| + } |
| + |
| + @Test |
| + public void testAnalyze_preset() { |
| + 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<Analysis.Token> tokens = analysis.analyze(text); |
| + assertNotNull(tokens); |
| + } |
| + |
| + @Test |
| + public void testAnalyze_custom() { |
| + AnalysisImpl analysis = new AnalysisImpl(); |
| + Map<String, String> tkParams = new HashMap<>(); |
| + tkParams.put("maxTokenLen", "128"); |
| + CustomAnalyzerConfig.Builder builder = new CustomAnalyzerConfig.Builder( |
| + "keyword", tkParams) |
| + .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<Analysis.Token> 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(); |
| + Map<String, String> tkParams = new HashMap<>(); |
| + tkParams.put("maxTokenLen", "128"); |
| + Map<String, String> tfParams = new HashMap<>(); |
| + tfParams.put("ignoreCase", "true"); |
| + tfParams.put("words", "stop.txt"); |
| + tfParams.put("format", "wordset"); |
| + CustomAnalyzerConfig.Builder builder = new CustomAnalyzerConfig.Builder( |
| + "whitespace", tkParams) |
| + .configDir(confDir.toString()) |
| + .addTokenFilterConfig("lowercase", Collections.emptyMap()) |
| + .addTokenFilterConfig("stop", tfParams); |
| + 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<Analysis.Token> 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/commits/CommitsImplTest.java b/lucene/luke/src/test/org/apache/lucene/luke/models/commits/CommitsImplTest.java |
| new file mode 100644 |
| index 00000000000..7e968d26fa2 |
| --- /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({ |
| + "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<Commit> commitList = commits.listCommits(); |
| + assertTrue(commitList.size() > 0); |
| + // should be sorted by descending order in generation |
| + assertEquals(commitList.size(), commitList.get(0).getGeneration()); |
| + assertEquals(1, commitList.get(commitList.size()-1).getGeneration()); |
| + } |
| + |
| + @Test |
| + public void testGetCommit() { |
| + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); |
| + Optional<Commit> 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<File> 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<Segment> 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<String, String> attributes = commits.getSegmentAttributes(1, "_0"); |
| + assertTrue(attributes.size() > 0); |
| + } |
| + |
| + @Test |
| + public void testGetSegmentAttributes_generation_notfound() { |
| + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); |
| + Map<String, String> attributes = commits.getSegmentAttributes(3, "_0"); |
| + assertTrue(attributes.isEmpty()); |
| + } |
| + |
| + @Test |
| + public void testGetSegmentAttributes_invalid_name() { |
| + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); |
| + Map<String, String> attributes = commits.getSegmentAttributes(1, "xxx"); |
| + assertTrue(attributes.isEmpty()); |
| + } |
| + |
| + @Test |
| + public void testGetSegmentDiagnostics() { |
| + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); |
| + Map<String, String> 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<String, String> diagnostics = commits.getSegmentDiagnostics(1,"xxx"); |
| + assertTrue(diagnostics.isEmpty()); |
| + } |
| + |
| + @Test |
| + public void testSegmentCodec() { |
| + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); |
| + Optional<Codec> codec = commits.getSegmentCodec(1, "_0"); |
| + assertTrue(codec.isPresent()); |
| + } |
| + |
| + @Test |
| + public void testSegmentCodec_generation_notfound() { |
| + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); |
| + Optional<Codec> codec = commits.getSegmentCodec(10, "_0"); |
| + assertFalse(codec.isPresent()); |
| + } |
| + |
| + @Test |
| + public void testSegmentCodec_invalid_name() { |
| + CommitsImpl commits = new CommitsImpl(reader, indexDir.toString()); |
| + Optional<Codec> 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 00000000000..e6349bf1e9d |
| --- /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 00000000000..730d251949c |
| --- /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/133 |
| +@LuceneTestCase.SuppressCodecs({ |
| + "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<DocumentField> 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<TermPosting> 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 00000000000..58519fa90ed |
| --- /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 00000000000..5d85e854bb0 |
| --- /dev/null |
| +++ b/lucene/luke/src/test/org/apache/lucene/luke/models/documents/TermVectorsAdapterTest.java |
| @@ -0,0 +1,165 @@ |
| +/* |
| + * 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.junit.Test; |
| + |
| +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<TermVectorEntry> tvEntries = adapterImpl.getTermVector(0, "text1"); |
| + |
| + assertEquals(18, tvEntries.size()); |
| + |
| + assertEquals("a", tvEntries.get(0).getTermText()); |
| + assertEquals(4, tvEntries.get(0).getFreq()); |
| + |
| + assertEquals("acknowledged", tvEntries.get(1).getTermText()); |
| + assertEquals(1, tvEntries.get(1).getFreq()); |
| + |
| + 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()); |
| + |
| + assertEquals("in", tvEntries.get(5).getTermText()); |
| + assertEquals(2, tvEntries.get(5).getFreq()); |
| + |
| + assertEquals("is", tvEntries.get(6).getTermText()); |
| + assertEquals(1, tvEntries.get(6).getFreq()); |
| + |
| + assertEquals("it", tvEntries.get(7).getTermText()); |
| + assertEquals(1, tvEntries.get(7).getFreq()); |
| + |
| + assertEquals("man", tvEntries.get(8).getTermText()); |
| + assertEquals(1, tvEntries.get(8).getFreq()); |
| + |
| + assertEquals("must", tvEntries.get(9).getTermText()); |
| + assertEquals(1, tvEntries.get(9).getFreq()); |
| + |
| + assertEquals("of", tvEntries.get(10).getTermText()); |
| + assertEquals(1, tvEntries.get(2).getFreq()); |
| + |
| + assertEquals("possession", tvEntries.get(11).getTermText()); |
| + assertEquals(1, tvEntries.get(11).getFreq()); |
| + |
| + assertEquals("single", tvEntries.get(12).getTermText()); |
| + assertEquals(1, tvEntries.get(12).getFreq()); |
| + |
| + assertEquals("that", tvEntries.get(13).getTermText()); |
| + assertEquals(1, tvEntries.get(13).getFreq()); |
| + |
| + assertEquals("truth", tvEntries.get(14).getTermText()); |
| + assertEquals(1, tvEntries.get(14).getFreq()); |
| + |
| + assertEquals("universally", tvEntries.get(15).getTermText()); |
| + assertEquals(1, tvEntries.get(15).getFreq()); |
| + |
| + assertEquals("want", tvEntries.get(16).getTermText()); |
| + assertEquals(1, tvEntries.get(16).getFreq()); |
| + |
| + assertEquals("wife", tvEntries.get(17).getTermText()); |
| + assertEquals(1, tvEntries.get(17).getFreq()); |
| + } |
| + |
| + @Test |
| + public void testGetTermVector_with_positions() throws Exception { |
| + TermVectorsAdapter adapterImpl = new TermVectorsAdapter(reader); |
| + List<TermVectorEntry> tvEntries = adapterImpl.getTermVector(0, "text2"); |
| + |
| + 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()); |
| + assertFalse(tvEntries.get(1).getPositions().get(0).getStartOffset().isPresent()); |
| + assertFalse(tvEntries.get(1).getPositions().get(0).getEndOffset().isPresent()); |
| + } |
| + |
| + @Test |
| + public void testGetTermVector_with_positions_offsets() throws Exception { |
| + TermVectorsAdapter adapterImpl = new TermVectorsAdapter(reader); |
| + List<TermVectorEntry> 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 00000000000..6e4522b8156 |
| --- /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<String, Long> 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<TermStats> 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 00000000000..5554d709941 |
| --- /dev/null |
| +++ b/lucene/luke/src/test/org/apache/lucene/luke/models/overview/OverviewTestBase.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.overview; |
| + |
| +import java.io.IOException; |
| +import java.nio.file.Path; |
| +import java.util.HashMap; |
| +import java.util.Map; |
| + |
| +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<String, String> userData = new HashMap<>(); |
| + userData.put("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 00000000000..0ccfd5e67ce |
| --- /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<String, Long> 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<String, Long> 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<String, Long> 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<String, Long> 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 00000000000..a726ad87a33 |
| --- /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<TermStats> 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 00000000000..e9603cf4b3e |
| --- /dev/null |
| +++ b/lucene/luke/src/test/org/apache/lucene/luke/models/search/SearchImplTest.java |
| @@ -0,0 +1,380 @@ |
| +/* |
| + * 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<String, Class<? extends Number>> 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, true); |
| + |
| + assertEquals(10, res.getTotalHits().value); |
| + 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, true); |
| + |
| + assertEquals(10, res.getTotalHits().value); |
| + 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, true); |
| + Optional<SearchResults> opt = search.nextPage(); |
| + assertTrue(opt.isPresent()); |
| + |
| + SearchResults res = opt.get(); |
| + assertEquals(20, res.getTotalHits().value); |
| + 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, true); |
| + 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, true); |
| + search.nextPage(); |
| + Optional<SearchResults> opt = search.prevPage(); |
| + assertTrue(opt.isPresent()); |
| + |
| + SearchResults res = opt.get(); |
| + assertEquals(20, res.getTotalHits().value); |
| + 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, true); |
| + assertFalse(search.prevPage().isPresent()); |
| + } |
| + |
| +} |
| diff --git a/lucene/module-build.xml b/lucene/module-build.xml |
| index d5798debf9f..0e6e6939773 100644 |
| --- a/lucene/module-build.xml |
| +++ b/lucene/module-build.xml |
| @@ -714,4 +714,26 @@ |
| </ant> |
| <property name="suggest-javadocs.uptodate" value="true"/> |
| </target> |
| + |
| + <property name="luke.jar" value="${common.dir}/build/luke/lucene-luke-${version}.jar"/> |
| + <target name="check-luke-uptodate" unless="luke.uptodate"> |
| + <module-uptodate name="luke" jarfile="${luke.jar}" property="luke.uptodate"/> |
| + </target> |
| + <target name="jar-luke" unless="luke.uptodate" depends="check-luke-uptodate"> |
| + <ant dir="${common.dir}/luke" target="jar-core" inheritAll="false"> |
| + <propertyset refid="uptodate.and.compiled.properties"/> |
| + </ant> |
| + <property name="luke.uptodate" value="true"/> |
| + </target> |
| + |
| + <property name="luke-javadoc.jar" value="${common.dir}/build/luke/lucene-luke-${version}-javadoc.jar"/> |
| + <target name="check-luke-javadocs-uptodate" unless="luke-javadocs.uptodate"> |
| + <module-uptodate name="luke" jarfile="${luke-javadoc.jar}" property="luke-javadocs.uptodate"/> |
| + </target> |
| + <target name="javadocs-luke" unless="luke-javadocs.uptodate" depends="check-luke-javadocs-uptodate"> |
| + <ant dir="${common.dir}/luke" target="javadocs" inheritAll="false"> |
| + <propertyset refid="uptodate.and.compiled.properties"/> |
| + </ant> |
| + <property name="luke-javadocs.uptodate" value="true"/> |
| + </target> |
| </project> |
| diff --git a/lucene/tools/junit4/tests.policy b/lucene/tools/junit4/tests.policy |
| index c698c2cd47a..74949813b7c 100644 |
| --- a/lucene/tools/junit4/tests.policy |
| +++ b/lucene/tools/junit4/tests.policy |
| @@ -66,7 +66,11 @@ 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 java.lang.RuntimePermission "setContextClassLoader"; |
| + |
| // read access to all system properties: |
| permission java.util.PropertyPermission "*", "read"; |
| // write access to only these: |