/** * Returns the next String in lexicographic order that will not put the machine into a reject * state. * * <p>This method traverses the DFA from the given position in the String, starting at the given * state. * * <p>If this cannot satisfy the machine, returns false. This method will walk the minimal path, * in lexicographic order, as long as possible. * * <p>If this method returns false, then there might still be more solutions, it is necessary to * backtrack to find out. * * @param state current non-reject state * @param position useful portion of the string * @return true if more possible solutions exist for the DFA from this position */ private boolean nextString(int state, int position) { /* * the next lexicographic character must be greater than the existing * character, if it exists. */ int c = 0; if (position < seekBytesRef.length) { c = seekBytesRef.bytes[position] & 0xff; // if the next byte is 0xff and is not part of the useful portion, // then by definition it puts us in a reject state, and therefore this // path is dead. there cannot be any higher transitions. backtrack. if (c++ == 0xff) return false; } seekBytesRef.length = position; visited[state] = curGen; Transition transitions[] = allTransitions[state]; // find the minimal path (lexicographic order) that is >= c for (int i = 0; i < transitions.length; i++) { Transition transition = transitions[i]; if (transition.getMax() >= c) { int nextChar = Math.max(c, transition.getMin()); // append either the next sequential char, or the minimum transition seekBytesRef.grow(seekBytesRef.length + 1); seekBytesRef.length++; seekBytesRef.bytes[seekBytesRef.length - 1] = (byte) nextChar; state = transition.getDest().getNumber(); /* * as long as is possible, continue down the minimal path in * lexicographic order. if a loop or accept state is encountered, stop. */ while (visited[state] != curGen && !runAutomaton.isAccept(state)) { visited[state] = curGen; /* * Note: we work with a DFA with no transitions to dead states. * so the below is ok, if it is not an accept state, * then there MUST be at least one transition. */ transition = allTransitions[state][0]; state = transition.getDest().getNumber(); // append the minimum transition seekBytesRef.grow(seekBytesRef.length + 1); seekBytesRef.length++; seekBytesRef.bytes[seekBytesRef.length - 1] = (byte) transition.getMin(); // we found a loop, record it for faster enumeration if (!finite && !linear && visited[state] == curGen) { setLinear(seekBytesRef.length - 1); } } return true; } } return false; }
/** * Sets the enum to operate in linear fashion, as we have found a looping transition at position: * we set an upper bound and act like a TermRangeQuery for this portion of the term space. */ private void setLinear(int position) { assert linear == false; int state = runAutomaton.getInitialState(); int maxInterval = 0xff; for (int i = 0; i < position; i++) { state = runAutomaton.step(state, seekBytesRef.bytes[i] & 0xff); assert state >= 0 : "state=" + state; } for (int i = 0; i < allTransitions[state].length; i++) { Transition t = allTransitions[state][i]; if (t.getMin() <= (seekBytesRef.bytes[position] & 0xff) && (seekBytesRef.bytes[position] & 0xff) <= t.getMax()) { maxInterval = t.getMax(); break; } } // 0xff terms don't get the optimization... not worth the trouble. if (maxInterval != 0xff) maxInterval++; int length = position + 1; /* position + maxTransition */ if (linearUpperBound.bytes.length < length) linearUpperBound.bytes = new byte[length]; System.arraycopy(seekBytesRef.bytes, 0, linearUpperBound.bytes, 0, position); linearUpperBound.bytes[position] = (byte) maxInterval; linearUpperBound.length = length; linear = true; }