package org.xbib.elasticsearch.common.fsa; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Iterator; import java.util.NoSuchElementException; /** * An iterator that traverses the right language of a given node (all sequences * reachable from a given node). */ public final class FSAFinalStatesIterator implements Iterator<ByteBuffer> { /** * Default expected depth of the recursion stack (estimated longest sequence * in the automaton). Buffers expand by the same value if exceeded. */ private static final int EXPECTED_MAX_STATES = 15; /** * The FSA to which this iterator belongs. */ private final FSA fsa; /** * An internal cache for the next element in the FSA */ private ByteBuffer nextElement; /** * A buffer for the current sequence of bytes from the current node to the * root. */ private byte[] buffer = new byte[EXPECTED_MAX_STATES]; /** * Reusable byte buffer wrapper around {@link #buffer}. */ private ByteBuffer bufferWrapper = ByteBuffer.wrap(buffer); /** * An arc stack for DFS when processing the automaton. */ private int[] arcs = new int[EXPECTED_MAX_STATES]; /** * Current processing depth in {@link #arcs}. */ private int position; /** * Create an instance of the iterator for a given node. * * @param fsa fsa * @param node node */ public FSAFinalStatesIterator(FSA fsa, int node) { this.fsa = fsa; if (fsa.getFirstArc(node) != 0) { restartFrom(node); } } /** * Restart walking from <code>node</code>. Allows iterator reuse. * * @param node node */ public void restartFrom(int node) { position = 0; bufferWrapper.clear(); nextElement = null; pushNode(node); } /** * Returns <code>true</code> if there are still elements in this iterator. */ @Override public boolean hasNext() { if (nextElement == null) { nextElement = advance(); } return nextElement != null; } /** * @return Returns a {@link java.nio.ByteBuffer} with the sequence corresponding to * the next final state in the automaton. */ @Override public ByteBuffer next() { if (nextElement != null) { final ByteBuffer cache = nextElement; nextElement = null; return cache; } else { final ByteBuffer cache = advance(); if (cache == null) { throw new NoSuchElementException(); } return cache; } } /** * Advances to the next available final state. */ private ByteBuffer advance() { if (position == 0) { return null; } while (position > 0) { final int lastIndex = position - 1; final int arc = arcs[lastIndex]; if (arc == 0) { // Remove the current node from the queue. position--; continue; } // Go to the next arc, but leave it on the stack // so that we keep the recursion depth level accurate. arcs[lastIndex] = fsa.getNextArc(arc); // Expand buffer if needed. final int bufferLength = this.buffer.length; if (lastIndex >= bufferLength) { this.buffer = Arrays.copyOf(buffer, bufferLength + EXPECTED_MAX_STATES); this.bufferWrapper = ByteBuffer.wrap(buffer); } buffer[lastIndex] = fsa.getArcLabel(arc); if (!fsa.isArcTerminal(arc)) { // Recursively descend into the arc's node. pushNode(fsa.getEndNode(arc)); } if (fsa.isArcFinal(arc)) { bufferWrapper.clear(); bufferWrapper.limit(lastIndex + 1); return bufferWrapper; } } return null; } /** * Not implemented in this iterator. */ @Override public void remove() { throw new UnsupportedOperationException("Read-only iterator."); } /** * Descends to a given node, adds its arcs to the stack to be traversed. */ private void pushNode(int node) { // Expand buffers if needed. if (position == arcs.length) { arcs = Arrays.copyOf(arcs, arcs.length + EXPECTED_MAX_STATES); } arcs[position++] = fsa.getFirstArc(node); } }