diff --git a/lucene/src/java/org/apache/lucene/util/automaton/SpecialOperations.java b/lucene/src/java/org/apache/lucene/util/automaton/SpecialOperations.java index ffd3040f338..d400b911dbc 100644 --- a/lucene/src/java/org/apache/lucene/util/automaton/SpecialOperations.java +++ b/lucene/src/java/org/apache/lucene/util/automaton/SpecialOperations.java @@ -29,6 +29,7 @@ package org.apache.lucene.util.automaton; +import java.util.BitSet; import java.util.HashMap; import java.util.HashSet; import java.util.Set; @@ -65,7 +66,7 @@ final public class SpecialOperations { */ public static boolean isFinite(Automaton a) { if (a.isSingleton()) return true; - return isFinite(a.initial, new HashSet()); + return isFinite(a.initial, new BitSet(a.getNumberOfStates()), new BitSet(a.getNumberOfStates())); } /** @@ -74,11 +75,12 @@ final public class SpecialOperations { */ // TODO: not great that this is recursive... in theory a // large automata could exceed java's stack - private static boolean isFinite(State s, HashSet path) { - path.add(s); + private static boolean isFinite(State s, BitSet path, BitSet visited) { + path.set(s.number); for (Transition t : s.getTransitions()) - if (path.contains(t.to) || !isFinite(t.to, path)) return false; - path.remove(s); + if (path.get(t.to.number) || (!visited.get(t.to.number) && !isFinite(t.to, path, visited))) return false; + path.clear(s.number); + visited.set(s.number); return true; } diff --git a/lucene/src/test-framework/org/apache/lucene/util/automaton/AutomatonTestUtil.java b/lucene/src/test-framework/org/apache/lucene/util/automaton/AutomatonTestUtil.java index 69704f06c09..ca943fff2ba 100644 --- a/lucene/src/test-framework/org/apache/lucene/util/automaton/AutomatonTestUtil.java +++ b/lucene/src/test-framework/org/apache/lucene/util/automaton/AutomatonTestUtil.java @@ -373,4 +373,28 @@ public class AutomatonTestUtil { a.removeDeadTransitions(); } + /** + * Returns true if the language of this automaton is finite. + *

+ * WARNING: this method is slow, it will blow up if the automaton is large. + * this is only used to test the correctness of our faster implementation. + */ + public static boolean isFiniteSlow(Automaton a) { + if (a.isSingleton()) return true; + return isFiniteSlow(a.initial, new HashSet()); + } + + /** + * Checks whether there is a loop containing s. (This is sufficient since + * there are never transitions to dead states.) + */ + // TODO: not great that this is recursive... in theory a + // large automata could exceed java's stack + private static boolean isFiniteSlow(State s, HashSet path) { + path.add(s); + for (Transition t : s.getTransitions()) + if (path.contains(t.to) || !isFiniteSlow(t.to, path)) return false; + path.remove(s); + return true; + } } diff --git a/lucene/src/test/org/apache/lucene/util/automaton/TestSpecialOperations.java b/lucene/src/test/org/apache/lucene/util/automaton/TestSpecialOperations.java new file mode 100644 index 00000000000..a7d4e66749d --- /dev/null +++ b/lucene/src/test/org/apache/lucene/util/automaton/TestSpecialOperations.java @@ -0,0 +1,34 @@ +package org.apache.lucene.util.automaton; + +import org.apache.lucene.util.LuceneTestCase; + +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class TestSpecialOperations extends LuceneTestCase { + /** + * tests against the original brics implementation. + */ + public void testIsFinite() { + int num = 2000 * RANDOM_MULTIPLIER; + for (int i = 0; i < num; i++) { + Automaton a = AutomatonTestUtil.randomAutomaton(random); + Automaton b = a.clone(); + assertEquals(AutomatonTestUtil.isFiniteSlow(a), SpecialOperations.isFinite(b)); + } + } +}