diff --git a/lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java b/lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java index adf30baec294..3b5d9486924d 100644 --- a/lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java +++ b/lucene/core/src/java/org/apache/lucene/util/QueryBuilder.java @@ -38,6 +38,7 @@ import org.apache.lucene.search.Query; import org.apache.lucene.search.SynonymQuery; import org.apache.lucene.search.TermQuery; +import org.apache.lucene.util.graph.GraphException; import org.apache.lucene.util.graph.GraphTokenStreamFiniteStrings; /** @@ -350,7 +351,7 @@ protected Query createFieldQuery( return analyzeMultiBoolean(field, stream, operator); } } - } catch (IOException e) { + } catch (IOException | GraphException e) { throw new RuntimeException("Error analyzing query text", e); } } @@ -489,7 +490,7 @@ protected Query analyzeMultiPhrase(String field, TokenStream stream, int slop) * query. */ protected Query analyzeGraphBoolean( - String field, TokenStream source, BooleanClause.Occur operator) throws IOException { + String field, TokenStream source, BooleanClause.Occur operator) throws IOException, GraphException { source.reset(); GraphTokenStreamFiniteStrings graph = new GraphTokenStreamFiniteStrings(source); BooleanQuery.Builder builder = new BooleanQuery.Builder(); diff --git a/lucene/core/src/java/org/apache/lucene/util/graph/GraphException.java b/lucene/core/src/java/org/apache/lucene/util/graph/GraphException.java new file mode 100644 index 000000000000..21d500f69cae --- /dev/null +++ b/lucene/core/src/java/org/apache/lucene/util/graph/GraphException.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.util.graph; + +/** Thrown when graph token persing encounters an error. */ +public class GraphException extends Exception { + public GraphException() { + super(); + } + + public GraphException(String message) { + super(message); + } + + public GraphException(String message, Throwable cause) { + super(message, cause); + } + + public GraphException(Throwable cause) { + super(cause); + } +} diff --git a/lucene/core/src/java/org/apache/lucene/util/graph/GraphTokenStreamFiniteStrings.java b/lucene/core/src/java/org/apache/lucene/util/graph/GraphTokenStreamFiniteStrings.java index 048cf0ab00c9..059c20996d3e 100644 --- a/lucene/core/src/java/org/apache/lucene/util/graph/GraphTokenStreamFiniteStrings.java +++ b/lucene/core/src/java/org/apache/lucene/util/graph/GraphTokenStreamFiniteStrings.java @@ -161,7 +161,7 @@ public TokenStream next() { * Returns the articulation points (or cut vertices) of the graph: * https://en.wikipedia.org/wiki/Biconnected_component */ - public int[] articulationPoints() { + public int[] articulationPoints() throws GraphException { if (det.getNumStates() == 0) { return new int[0]; } @@ -260,7 +260,7 @@ private static void articulationPointsRecurse( int[] low, int[] parent, BitSet visited, - List points) { + List points) throws GraphException { visited.set(state); depth[state] = d; low[state] = d; @@ -275,7 +275,7 @@ private static void articulationPointsRecurse( if (d < MAX_RECURSION_LEVEL) { articulationPointsRecurse(a, t.dest, d + 1, depth, low, parent, visited, points); } else { - continue; + throw new GraphException("Exceeded maximum recursion level"); } childCount++; if (low[t.dest] >= depth[state]) { diff --git a/lucene/core/src/test/org/apache/lucene/util/graph/TestGraphTokenStreamFiniteStrings.java b/lucene/core/src/test/org/apache/lucene/util/graph/TestGraphTokenStreamFiniteStrings.java index 56fadc901445..eced7f06c372 100644 --- a/lucene/core/src/test/org/apache/lucene/util/graph/TestGraphTokenStreamFiniteStrings.java +++ b/lucene/core/src/test/org/apache/lucene/util/graph/TestGraphTokenStreamFiniteStrings.java @@ -20,6 +20,8 @@ import java.util.ArrayList; import java.util.Iterator; +import java.util.concurrent.CancellationException; + import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute; @@ -684,32 +686,6 @@ public void testLongTokenStreamStackOverflowError() throws Exception { TokenStream ts = new CannedTokenStream(tokens.toArray(new Token[0])); GraphTokenStreamFiniteStrings graph = new GraphTokenStreamFiniteStrings(ts); - Iterator it = graph.getFiniteStrings(); - assertTrue(it.hasNext()); - it.next(); - assertTrue(it.hasNext()); - it.next(); - assertFalse(it.hasNext()); - - int[] points = graph.articulationPoints(); // This will cause a java.lang.StackOverflowError - assertEquals(points[0], 1); - assertEquals(points[1], 3); - assertEquals(points.length, MAX_RECURSION_LEVEL - 2); - - assertFalse(graph.hasSidePath(0)); - it = graph.getFiniteStrings(0, 1); - assertTrue(it.hasNext()); - assertTokenStream(it.next(), new String[] {"fast"}, new int[] {1}); - assertFalse(it.hasNext()); - Term[] terms = graph.getTerms("field", 0); - assertArrayEquals(terms, new Term[] {new Term("field", "fast")}); - - assertTrue(graph.hasSidePath(1)); - it = graph.getFiniteStrings(1, 3); - assertTrue(it.hasNext()); - assertTokenStream(it.next(), new String[] {"wi", "fi"}, new int[] {1, 1}); - assertTrue(it.hasNext()); - assertTokenStream(it.next(), new String[] {"wifi"}, new int[] {1}); - assertFalse(it.hasNext()); + assertThrows(GraphException.class, () -> graph.articulationPoints()); } } diff --git a/lucene/queries/src/java/org/apache/lucene/queries/intervals/IntervalBuilder.java b/lucene/queries/src/java/org/apache/lucene/queries/intervals/IntervalBuilder.java index e76be6f9e609..3315d2ff9e90 100644 --- a/lucene/queries/src/java/org/apache/lucene/queries/intervals/IntervalBuilder.java +++ b/lucene/queries/src/java/org/apache/lucene/queries/intervals/IntervalBuilder.java @@ -54,6 +54,7 @@ import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.QueryVisitor; import org.apache.lucene.util.BytesRef; +import org.apache.lucene.util.graph.GraphException; import org.apache.lucene.util.graph.GraphTokenStreamFiniteStrings; /** @@ -67,7 +68,7 @@ */ final class IntervalBuilder { static IntervalsSource analyzeText(CachingTokenFilter stream, int maxGaps, boolean ordered) - throws IOException { + throws IOException, GraphException { assert stream != null; TermToBytesRefAttribute termAtt = stream.getAttribute(TermToBytesRefAttribute.class); @@ -198,7 +199,7 @@ private static IntervalsSource analyzeSynonyms(TokenStream ts, int maxGaps, bool return combineSources(terms, maxGaps, ordered); } - private static List analyzeGraph(TokenStream source) throws IOException { + private static List analyzeGraph(TokenStream source) throws IOException, GraphException { source.reset(); GraphTokenStreamFiniteStrings graph = new GraphTokenStreamFiniteStrings(source); diff --git a/lucene/queries/src/java/org/apache/lucene/queries/intervals/Intervals.java b/lucene/queries/src/java/org/apache/lucene/queries/intervals/Intervals.java index aa546a469c20..c0882fce3e52 100644 --- a/lucene/queries/src/java/org/apache/lucene/queries/intervals/Intervals.java +++ b/lucene/queries/src/java/org/apache/lucene/queries/intervals/Intervals.java @@ -32,6 +32,7 @@ import org.apache.lucene.util.automaton.CompiledAutomaton; import org.apache.lucene.util.automaton.LevenshteinAutomata; import org.apache.lucene.util.automaton.Operations; +import org.apache.lucene.util.graph.GraphException; /** * Factory functions for creating {@link IntervalsSource interval sources}. @@ -514,10 +515,11 @@ public static IntervalsSource after(IntervalsSource source, IntervalsSource refe * @return Returns an {@link IntervalsSource} that matches tokens acquired from analysis of {@code * text}. Possibly an empty interval source, never {@code null}. * @throws IOException If an I/O exception occurs. + * @throws GraphException If graph parsing fails. */ public static IntervalsSource analyzedText( String text, Analyzer analyzer, String field, int maxGaps, boolean ordered) - throws IOException { + throws IOException, GraphException { try (TokenStream ts = analyzer.tokenStream(field, text)) { return analyzedText(ts, maxGaps, ordered); } @@ -535,9 +537,10 @@ public static IntervalsSource analyzedText( * @return Returns an {@link IntervalsSource} that matches tokens acquired from analysis of {@code * text}. Possibly an empty interval source, never {@code null}. * @throws IOException If an I/O exception occurs. + * @throws GraphException If graph parsing fails. */ public static IntervalsSource analyzedText(TokenStream tokenStream, int maxGaps, boolean ordered) - throws IOException { + throws IOException, GraphException { CachingTokenFilter stream = tokenStream instanceof CachingTokenFilter ? (CachingTokenFilter) tokenStream diff --git a/lucene/queries/src/test/org/apache/lucene/queries/intervals/TestIntervalBuilder.java b/lucene/queries/src/test/org/apache/lucene/queries/intervals/TestIntervalBuilder.java index b74670a51bcf..53060aa0bc22 100644 --- a/lucene/queries/src/test/org/apache/lucene/queries/intervals/TestIntervalBuilder.java +++ b/lucene/queries/src/test/org/apache/lucene/queries/intervals/TestIntervalBuilder.java @@ -52,9 +52,10 @@ import org.apache.lucene.tests.analysis.Token; import org.apache.lucene.tests.index.RandomIndexWriter; import org.apache.lucene.tests.util.LuceneTestCase; +import org.apache.lucene.util.graph.GraphException; public class TestIntervalBuilder extends LuceneTestCase { - public void testSimpleTerm() throws IOException { + public void testSimpleTerm() throws IOException, GraphException { CannedTokenStream ts = new CannedTokenStream(new Token("term1", 1, 2)); IntervalsSource source = IntervalBuilder.analyzeText(new CachingTokenFilter(ts), -1, true); @@ -63,7 +64,7 @@ public void testSimpleTerm() throws IOException { assertEquals(expected, source); } - public void testOrdered() throws IOException { + public void testOrdered() throws IOException, GraphException { CannedTokenStream ts = new CannedTokenStream( new Token("term1", 1, 2), new Token("term2", 3, 4), new Token("term3", 5, 6)); @@ -76,7 +77,7 @@ public void testOrdered() throws IOException { assertEquals(expected, source); } - public void testUnordered() throws IOException { + public void testUnordered() throws IOException, GraphException { CannedTokenStream ts = new CannedTokenStream( new Token("term1", 1, 2), new Token("term2", 3, 4), new Token("term3", 5, 6)); @@ -89,7 +90,7 @@ public void testUnordered() throws IOException { assertEquals(expected, source); } - public void testPhrase() throws IOException { + public void testPhrase() throws IOException, GraphException { CannedTokenStream ts = new CannedTokenStream( new Token("term1", 1, 2), new Token("term2", 3, 4), new Token("term3", 5, 6)); @@ -101,7 +102,7 @@ public void testPhrase() throws IOException { assertEquals(expected, source); } - public void testPhraseWithStopword() throws IOException { + public void testPhraseWithStopword() throws IOException, GraphException { CannedTokenStream ts = new CannedTokenStream(new Token("term1", 1, 1, 2), new Token("term3", 2, 5, 6)); @@ -112,13 +113,13 @@ public void testPhraseWithStopword() throws IOException { assertEquals(expected, source); } - public void testEmptyTokenStream() throws IOException { + public void testEmptyTokenStream() throws IOException, GraphException { CannedTokenStream ts = new CannedTokenStream(); IntervalsSource source = IntervalBuilder.analyzeText(new CachingTokenFilter(ts), 0, true); assertSame(IntervalBuilder.NO_INTERVALS, source); } - public void testSimpleSynonyms() throws IOException { + public void testSimpleSynonyms() throws IOException, GraphException { CannedTokenStream ts = new CannedTokenStream( new Token("term1", 1, 2), @@ -136,7 +137,7 @@ public void testSimpleSynonyms() throws IOException { assertEquals(expected, source); } - public void testSimpleSynonymsWithGap() throws IOException { + public void testSimpleSynonymsWithGap() throws IOException, GraphException { // term1 [] term2/term3/term4 term5 CannedTokenStream ts = new CannedTokenStream( @@ -159,7 +160,7 @@ public void testSimpleSynonymsWithGap() throws IOException { assertEquals(expected, source); } - public void testGraphSynonyms() throws IOException { + public void testGraphSynonyms() throws IOException, GraphException { // term1 term2:2/term3 term4 term5 CannedTokenStream ts = new CannedTokenStream( @@ -179,7 +180,7 @@ public void testGraphSynonyms() throws IOException { assertEquals(expected, source); } - public void testGraphSynonymsWithGaps() throws IOException { + public void testGraphSynonymsWithGaps() throws IOException, GraphException { // term1 [] term2:4/term3 [] [] term4 term5 CannedTokenStream ts = new CannedTokenStream( @@ -203,7 +204,7 @@ public void testGraphSynonymsWithGaps() throws IOException { assertEquals(expected, source); } - public void testGraphTerminatesOnGap() throws IOException { + public void testGraphTerminatesOnGap() throws IOException, GraphException { // term1 term2:2/term3 term4 [] term5 CannedTokenStream ts = new CannedTokenStream( @@ -222,7 +223,7 @@ public void testGraphTerminatesOnGap() throws IOException { assertEquals(expected, source); } - public void testEmptyIntervals() throws IOException { + public void testEmptyIntervals() throws IOException, GraphException { CannedTokenStream ts = new CannedTokenStream(); IntervalsSource source = IntervalBuilder.analyzeText(new CachingTokenFilter(ts), -1, true); diff --git a/lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/nodes/intervalfn/AnalyzedText.java b/lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/nodes/intervalfn/AnalyzedText.java index 1aee75c42f73..02fa06ea8bd9 100644 --- a/lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/nodes/intervalfn/AnalyzedText.java +++ b/lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/standard/nodes/intervalfn/AnalyzedText.java @@ -21,6 +21,7 @@ import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.queries.intervals.Intervals; import org.apache.lucene.queries.intervals.IntervalsSource; +import org.apache.lucene.util.graph.GraphException; /** Node that represents {@link Intervals#analyzedText(String, Analyzer, String, int, boolean)}. */ public class AnalyzedText extends IntervalFunction { @@ -36,7 +37,7 @@ public IntervalsSource toIntervalSource(String field, Analyzer analyzer) { boolean ordered = true; try { return Intervals.analyzedText(term, analyzer, field, gaps, ordered); - } catch (IOException e) { + } catch (IOException | GraphException e) { throw new RuntimeException(e); } }