@Override
public Automaton op(Automaton a) {
if (searchFor.length() == 0)
return emptyStringOp(a);
Automaton result = a.clone();
assert result.isDeterministic();
List<ConstrainedEpsilon> epsilons = new LinkedList<ConstrainedEpsilon>();
List<StateTransitionPair> killedTransitions = new LinkedList<StateTransitionPair>();
List<StateTransitionPair> newTransitions = new LinkedList<StateTransitionPair>();
String ghostString = searchFor.substring(0, searchFor.length() - 1);
for (State origin : result.getStates()) {
LinkedList<State> path = getPath(origin, searchFor);
if (path == null)
continue;
// create a path to read the replacement string
LinkedList<State> replacement = makeString(replaceBy);
// create a path to read part of the search string, in case
// only a prefix of the search string occurred
LinkedList<State> ghost = makeString(ghostString);
// connect to replacement string (head and tail)
epsilons.add(new ConstrainedEpsilon(origin, replacement.getFirst()));
epsilons.add(new ConstrainedEpsilon(replacement.getLast(), path.getLast()));
// set accept states of first and last
if (origin.isAccept()) {
replacement.getFirst().setAccept(true);
ghost.getFirst().setAccept(true);
}
if (path.getLast().isAccept()) {
replacement.getLast().setAccept(true);
}
// connect to ghost string (head)
epsilons.add(new ConstrainedEpsilon(origin, ghost.getFirst()));
// connect to successive states in the ghost state,
// and set accept states in the ghost
Iterator<State> pathIt = path.iterator();
Iterator<State> ghostIt = ghost.iterator();
ghostIt.next(); // skip the initial state in the ghost path
int index = 1;
while (ghostIt.hasNext()) {
assert pathIt.hasNext();
State pathState = pathIt.next();
State ghostState = ghostIt.next();
// add an epsilon transition, but disallow reading the next
// character from the search string if it is followed
epsilons.add(new ConstrainedEpsilon(ghostState, pathState, searchFor.charAt(index)));
// set accept state
if (pathState.isAccept()) {
ghostState.setAccept(true);
}
// next char in search string
index++;
}
// remove the transition with the first character of
// the search string from the origin state
char first = searchFor.charAt(0);
for (Transition tr : origin.getTransitions()) {
if (tr.getMin() <= first && tr.getMax() >= first) {
killedTransitions.add(new StateTransitionPair(origin, tr));
// add back the remaining characters from the interval
if (tr.getMin() < first) {
newTransitions.add(new StateTransitionPair(origin, new Transition(tr.getMin(), (char)(first-1), tr.getDest())));
}
if (tr.getMax() > first) {
newTransitions.add(new StateTransitionPair(origin, new Transition((char)(first+1), tr.getMax(), tr.getDest())));
}
}
}
}
// apply the first character removal first
for (StateTransitionPair pair : killedTransitions) {
pair.state.getTransitions().remove(pair.transition);
}
for (StateTransitionPair pair : newTransitions) {
pair.state.addTransition(pair.transition);
}
// apply epsilons
addConstrainedEpsilons(result, epsilons);
result.reduce();
result.minimize();
return result;
}