/* Created on Dec 21, 2012 by Florian Leitner.
* Copyright 2012. All rights reserved. */
package com.tuplejump.stargate.lucene.query.fsm;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* A State vertex with outgoing labeled (transition) and empty (epsilon) edges.
* <p>
* It should never be necessary to directly use this class of the generic FSA implementation.
* <p>
* By default, a new State is never final (i.e., does not have the "accept" flag set).
*
* @author Florian Leitner
*/
final class Node<E> {
private boolean accept = false;
boolean captureStart = false;
boolean captureEnd = false;
Map<Transition<E>, Set<Node<E>>> transitions = new HashMap<Transition<E>, Set<Node<E>>>();
Set<Node<E>> epsilonTransitions = new HashSet<Node<E>>();
/** A representation of a State for debugging purposes. */
@Override
public String toString() {
Set<Node<E>> visited = new HashSet<Node<E>>();
visited.add(this);
return toStringVisitor(visited, 0);
}
/** Representation recursion. */
String toStringVisitor(Set<Node<E>> visited, int level) {
StringBuffer sb = new StringBuffer();
List<Node<E>> toVisit = new LinkedList<Node<E>>();
sb.append(Node.class.getSimpleName());
sb.append(String.format("[acc=%s cap=%s%s e:%d t:%d]\n", accept, captureStart ? "(" : "",
captureEnd ? ")" : "", epsilonTransitions.size(), transitions.size()));
for (int i = 0; i < level; i++)
sb.append(" ");
sb.append("{");
boolean any = false;
level++;
for (Node<E> s : epsilonTransitions) {
if (!visited.contains(s)) {
visited.add(s);
toVisit.add(s);
}
}
for (Node<E> s : toVisit) {
any = true;
sb.append("\n");
for (int i = 0; i < level; i++)
sb.append(" ");
sb.append("epsilon => ");
sb.append(s.toStringVisitor(new HashSet<Node<E>>(visited), level));
}
toVisit.clear();
for (Transition<E> t : transitions.keySet()) {
for (Node<E> s : transitions.get(t)) {
if (!visited.contains(s)) {
visited.add(s);
toVisit.add(s);
}
}
}
for (Transition<E> t : transitions.keySet()) {
for (Node<E> s : transitions.get(t)) {
if (toVisit.contains(s)) {
any = true;
sb.append("\n");
for (int i = 0; i < level; i++)
sb.append(" ");
sb.append(t);
sb.append(" => ");
sb.append(s.toStringVisitor(new HashSet<Node<E>>(visited), level));
}
}
}
level--;
if (any) {
sb.append("\n");
for (int i = 0; i < level; i++)
sb.append(" ");
}
sb.append("}");
return sb.toString();
}
/** Make this state a final state (sets the "accept" flag). */
void makeFinal() {
accept = true;
}
/** Make this state a non-final state (removes the "accept" flag). */
void makeNonFinal() {
accept = false;
}
/** Return <code>true</code> if this is a final ("accept") state. */
boolean isFinal() {
return accept;
}
/**
* Add a transition <code>t</code> to another state <code>s</code> that only triggers if the
* element in the sequence {@link Transition#matches matches} the transition's requirements.
* <p>
* If the element matches, it is consumed.
*
* @param t transition (requirement)
* @param s target state (for the transition)
*/
void addTransition(Transition<E> t, Node<E> s) {
Set<Node<E>> nodeList;
if (transitions.containsKey(t)) {
nodeList = transitions.get(t);
} else {
nodeList = new HashSet<Node<E>>();
transitions.put(t, nodeList);
}
nodeList.add(s);
}
/** Add an empty (non-consuming) transition to another state. */
void addEpsilonTransition(Node<E> s) {
epsilonTransitions.add(s);
}
/** Return <code>true</code> if this state is the start or end of a capture group. */
public boolean isCapturing() {
return captureStart || captureEnd;
}
}