/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF 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.automaton; import java.util.BitSet; import org.apache.lucene.util.ArrayUtil; import org.apache.lucene.util.IntsRef; import org.apache.lucene.util.IntsRefBuilder; import org.apache.lucene.util.RamUsageEstimator; /** * Iterates all accepted strings. * * <p>If the {@link Automaton} has cycles then this iterator may throw an {@code * IllegalArgumentException}, but this is not guaranteed! * * <p>Be aware that the iteration order is implementation dependent * and may change across releases. * * <p>If the automaton is not determinized then it's possible this iterator * will return duplicates. * * @lucene.experimental */ public class FiniteStringsIterator { /** * Empty string. */ private static final IntsRef EMPTY = new IntsRef(); /** * Automaton to create finite string from. */ private final Automaton a; /** * The state where each path should stop or -1 if only accepted states should be final. */ private final int endState; /** * Tracks which states are in the current path, for cycle detection. */ private final BitSet pathStates; /** * Builder for current finite string. */ private final IntsRefBuilder string; /** * Stack to hold our current state in the recursion/iteration. */ private PathNode[] nodes; /** * Emit empty string?. */ private boolean emitEmptyString; /** * Constructor. * * @param a Automaton to create finite string from. */ public FiniteStringsIterator(Automaton a) { this(a, 0, -1); } /** * Constructor. * * @param a Automaton to create finite string from. * @param startState The starting state for each path. * @param endState The state where each path should stop or -1 if only accepted states should be final. */ public FiniteStringsIterator(Automaton a, int startState, int endState) { this.a = a; this.endState = endState; this.nodes = new PathNode[16]; for (int i = 0, end = nodes.length; i < end; i++) { nodes[i] = new PathNode(); } this.string = new IntsRefBuilder(); this.pathStates = new BitSet(a.getNumStates()); this.string.setLength(0); this.emitEmptyString = a.isAccept(0); // Start iteration with node startState. if (a.getNumTransitions(startState) > 0) { pathStates.set(startState); nodes[0].resetState(a, startState); string.append(startState); } } /** * Generate next finite string. * The return value is just valid until the next call of this method! * * @return Finite string or null, if no more finite strings are available. */ public IntsRef next() { // Special case the empty string, as usual: if (emitEmptyString) { emitEmptyString = false; return EMPTY; } for (int depth = string.length(); depth > 0;) { PathNode node = nodes[depth-1]; // Get next label leaving the current node: int label = node.nextLabel(a); if (label != -1) { string.setIntAt(depth - 1, label); int to = node.to; if (a.getNumTransitions(to) != 0 && to != endState) { // Now recurse: the destination of this transition has outgoing transitions: if (pathStates.get(to)) { throw new IllegalArgumentException("automaton has cycles"); } pathStates.set(to); // Push node onto stack: growStack(depth); nodes[depth].resetState(a, to); depth++; string.setLength(depth); string.grow(depth); } else if (endState == to || a.isAccept(to)) { // This transition leads to an accept state, so we save the current string: return string.get(); } } else { // No more transitions leaving this state, pop/return back to previous state: int state = node.state; assert pathStates.get(state); pathStates.clear(state); depth--; string.setLength(depth); if (a.isAccept(state)) { // This transition leads to an accept state, so we save the current string: return string.get(); } } } // Finished iteration. return null; } /** * Grow path stack, if required. */ private void growStack(int depth) { if (nodes.length == depth) { PathNode[] newNodes = new PathNode[ArrayUtil.oversize(nodes.length + 1, RamUsageEstimator.NUM_BYTES_OBJECT_REF)]; System.arraycopy(nodes, 0, newNodes, 0, nodes.length); for (int i = depth, end = newNodes.length; i < end; i++) { newNodes[i] = new PathNode(); } nodes = newNodes; } } /** * Nodes for path stack. */ private static class PathNode { /** Which state the path node ends on, whose * transitions we are enumerating. */ public int state; /** Which state the current transition leads to. */ public int to; /** Which transition we are on. */ public int transition; /** Which label we are on, in the min-max range of the * current Transition */ public int label; private final Transition t = new Transition(); public void resetState(Automaton a, int state) { assert a.getNumTransitions(state) != 0; this.state = state; transition = 0; a.getTransition(state, 0, t); label = t.min; to = t.dest; } /** Returns next label of current transition, or * advances to next transition and returns its first * label, if current one is exhausted. If there are * no more transitions, returns -1. */ public int nextLabel(Automaton a) { if (label > t.max) { // We've exhaused the current transition's labels; // move to next transitions: transition++; if (transition >= a.getNumTransitions(state)) { // We're done iterating transitions leaving this state label = -1; return -1; } a.getTransition(state, transition, t); label = t.min; to = t.dest; } return label++; } } }