From 7ac77ce4418c06d01af97663230b00c1b4b44d22 Mon Sep 17 00:00:00 2001 From: Tomoko Uchida Date: Sun, 30 Jun 2019 12:19:48 +0900 Subject: [PATCH] LUCENE-8894: Add APIs to tokenizer/charfilter/tokenfilter factories to get their SPI names from concrete classes --- lucene/CHANGES.txt | 2 + .../util/AbstractAnalysisFactory.java | 22 ++++++++++ .../analysis/util/AnalysisSPILoader.java | 22 +++------- .../analysis/util/CharFilterFactory.java | 9 ++++ .../analysis/util/TokenFilterFactory.java | 11 ++++- .../analysis/util/TokenizerFactory.java | 11 ++++- .../util/TestAbstractAnalysisFactory.java | 41 +++++++++++++++++++ 7 files changed, 100 insertions(+), 18 deletions(-) create mode 100644 lucene/analysis/common/src/test/org/apache/lucene/analysis/util/TestAbstractAnalysisFactory.java diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index aecf105fecd..b761ae3f274 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -138,6 +138,8 @@ Improvements * LUCENE-8855: Add Accountable to some Query implementations (ab, Adrien Grand) +* LUCENE-8894: Add APIs to find SPI names for Tokenizer/CharFilter/TokenFilter factory classes. (Tomoko Uchida) + Optimizations * LUCENE-8796: Use exponential search instead of binary search in diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/AbstractAnalysisFactory.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/AbstractAnalysisFactory.java index dfff3fad03b..8a4f84914f6 100644 --- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/AbstractAnalysisFactory.java +++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/AbstractAnalysisFactory.java @@ -21,6 +21,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.nio.charset.CharsetDecoder; import java.nio.charset.CodingErrorAction; import java.nio.charset.StandardCharsets; @@ -31,6 +33,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -335,4 +338,23 @@ public abstract class AbstractAnalysisFactory { public void setExplicitLuceneMatchVersion(boolean isExplicitLuceneMatchVersion) { this.isExplicitLuceneMatchVersion = isExplicitLuceneMatchVersion; } + + /** + * Looks up SPI name (static "NAME" field) with appropriate modifiers. + * Also it must be a String class and declared in the concrete class. + * @return the SPI name + * @throws NoSuchFieldException - if the "NAME" field is not defined. + * @throws IllegalAccessException - if the "NAME" field is inaccessible. + * @throws IllegalStateException - if the "NAME" field does not have appropriate modifiers or isn't a String field. + */ + static String lookupSPIName(Class service) throws NoSuchFieldException, IllegalAccessException, IllegalStateException { + final Field field = service.getField("NAME"); + int modifier = field.getModifiers(); + if (Modifier.isStatic(modifier) && Modifier.isFinal(modifier) && + field.getType().equals(String.class) && + Objects.equals(field.getDeclaringClass(), service)) { + return ((String) field.get(null)); + } + throw new IllegalStateException("No SPI name defined."); + } } diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/AnalysisSPILoader.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/AnalysisSPILoader.java index 8e79c3e107b..db420cc9759 100644 --- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/AnalysisSPILoader.java +++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/AnalysisSPILoader.java @@ -17,9 +17,7 @@ package org.apache.lucene.analysis.util; -import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Modifier; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Locale; @@ -83,21 +81,13 @@ public final class AnalysisSPILoader { String originalName = null; Throwable cause = null; try { - // Lookup "NAME" field with appropriate modifiers. - // Also it must be a String class and declared in the service class. - final Field field = service.getField("NAME"); - int modifier = field.getModifiers(); - if (Modifier.isStatic(modifier) && Modifier.isFinal(modifier) && - field.getType().equals(String.class) && - Objects.equals(field.getDeclaringClass(), service)) { - originalName = ((String)field.get(null)); - name = originalName.toLowerCase(Locale.ROOT); - if (!isValidName(originalName)) { - throw new ServiceConfigurationError("The name " + originalName + " for " + service.getName() + - " is invalid: Allowed characters are (English) alphabet, digits, and underscore. It should be started with an alphabet."); - } + originalName = AbstractAnalysisFactory.lookupSPIName(service); + name = originalName.toLowerCase(Locale.ROOT); + if (!isValidName(originalName)) { + throw new ServiceConfigurationError("The name " + originalName + " for " + service.getName() + + " is invalid: Allowed characters are (English) alphabet, digits, and underscore. It should be started with an alphabet."); } - } catch (NoSuchFieldException | IllegalAccessException e) { + } catch (NoSuchFieldException | IllegalAccessException | IllegalStateException e) { cause = e; } if (name == null) { diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/CharFilterFactory.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/CharFilterFactory.java index 7a5eccb1543..722025c1107 100644 --- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/CharFilterFactory.java +++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/CharFilterFactory.java @@ -49,6 +49,15 @@ public abstract class CharFilterFactory extends AbstractAnalysisFactory { return loader.availableServices(); } + /** looks up a SPI name for the specified char filter factory */ + public static String findSPIName(Class serviceClass) { + try { + return lookupSPIName(serviceClass); + } catch (NoSuchFieldException | IllegalAccessException | IllegalStateException e) { + throw new IllegalStateException(e); + } + } + /** * Reloads the factory list from the given {@link ClassLoader}. * Changes to the factories are visible after the method ends, all diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/TokenFilterFactory.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/TokenFilterFactory.java index 72c407094b8..1d7d5c76ffc 100644 --- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/TokenFilterFactory.java +++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/TokenFilterFactory.java @@ -47,7 +47,16 @@ public abstract class TokenFilterFactory extends AbstractAnalysisFactory { public static Set availableTokenFilters() { return loader.availableServices(); } - + + /** looks up a SPI name for the specified token filter factory */ + public static String findSPIName(Class serviceClass) { + try { + return lookupSPIName(serviceClass); + } catch (NoSuchFieldException | IllegalAccessException | IllegalStateException e) { + throw new IllegalStateException(e); + } + } + /** * Reloads the factory list from the given {@link ClassLoader}. * Changes to the factories are visible after the method ends, all diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/TokenizerFactory.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/TokenizerFactory.java index 8b3e1f9c920..2cd3a15a0aa 100644 --- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/TokenizerFactory.java +++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/util/TokenizerFactory.java @@ -49,7 +49,16 @@ public abstract class TokenizerFactory extends AbstractAnalysisFactory { public static Set availableTokenizers() { return loader.availableServices(); } - + + /** looks up a SPI name for the specified tokenizer factory */ + public static String findSPIName(Class serviceClass) { + try { + return lookupSPIName(serviceClass); + } catch (NoSuchFieldException | IllegalAccessException | IllegalStateException e) { + throw new IllegalStateException(e); + } + } + /** * Reloads the factory list from the given {@link ClassLoader}. * Changes to the factories are visible after the method ends, all diff --git a/lucene/analysis/common/src/test/org/apache/lucene/analysis/util/TestAbstractAnalysisFactory.java b/lucene/analysis/common/src/test/org/apache/lucene/analysis/util/TestAbstractAnalysisFactory.java new file mode 100644 index 00000000000..b8d028eb9ae --- /dev/null +++ b/lucene/analysis/common/src/test/org/apache/lucene/analysis/util/TestAbstractAnalysisFactory.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.analysis.util; + +import org.apache.lucene.analysis.charfilter.HTMLStripCharFilterFactory; +import org.apache.lucene.analysis.core.LowerCaseFilterFactory; +import org.apache.lucene.analysis.core.WhitespaceTokenizerFactory; +import org.apache.lucene.util.LuceneTestCase; + +public class TestAbstractAnalysisFactory extends LuceneTestCase { + + public void testLookupTokenizerSPIName() throws NoSuchFieldException, IllegalAccessException { + assertEquals("whitespace", AbstractAnalysisFactory.lookupSPIName(WhitespaceTokenizerFactory.class)); + assertEquals("whitespace", TokenizerFactory.findSPIName(WhitespaceTokenizerFactory.class)); + } + + public void testLookupCharFilterSPIName() throws NoSuchFieldException, IllegalAccessException { + assertEquals("htmlStrip", AbstractAnalysisFactory.lookupSPIName(HTMLStripCharFilterFactory.class)); + assertEquals("htmlStrip", CharFilterFactory.findSPIName(HTMLStripCharFilterFactory.class)); + } + + public void testLookupTokenFilterSPIName() throws NoSuchFieldException, IllegalAccessException{ + assertEquals("lowercase", AbstractAnalysisFactory.lookupSPIName(LowerCaseFilterFactory.class)); + assertEquals("lowercase", TokenFilterFactory.findSPIName(LowerCaseFilterFactory.class)); + } +}