/**
* This file Copyright (c) 2005-2008 Aptana, Inc. This program is
* dual-licensed under both the Aptana Public License and the GNU General
* Public license. You may elect to use one or the other of these licenses.
*
* This program is distributed in the hope that it will be useful, but
* AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or
* NONINFRINGEMENT. Redistribution, except as permitted by whichever of
* the GPL or APL you select, is prohibited.
*
* 1. For the GPL license (GPL), you can redistribute and/or modify this
* program under the terms of the GNU General Public License,
* Version 3, as published by the Free Software Foundation. You should
* have received a copy of the GNU General Public License, Version 3 along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Aptana provides a special exception to allow redistribution of this file
* with certain other free and open source software ("FOSS") code and certain additional terms
* pursuant to Section 7 of the GPL. You may view the exception and these
* terms on the web at http://www.aptana.com/legal/gpl/.
*
* 2. For the Aptana Public License (APL), this program and the
* accompanying materials are made available under the terms of the APL
* v1.0 which accompanies this distribution, and is available at
* http://www.aptana.com/legal/apl/.
*
* You may view the GPL, Aptana's exception and additional terms, and the
* APL in the file titled license.html at the root of the corresponding
* plugin containing this source file.
*
* Any modifications to this file must keep this entire header intact.
*/
package com.aptana.ide.regex.nfa;
import java.util.Stack;
import com.aptana.ide.regex.Closure;
import com.aptana.ide.regex.inputs.Input;
import com.aptana.ide.regex.sets.CharacterSet;
/**
* @author Kevin Lindsey
*/
public class NFAGraph
{
static Stack<Integer> recycle = new Stack<Integer>();
static NFANode[] nodes = new NFANode[0];
int _start;
int _end;
/**
* Return the AcceptState for the given node index
*
* @param nodeIndex
* The node index we wish to retrieve
* @return The AcceptState for the given node index
*/
public int getAcceptState(int nodeIndex)
{
return this.getItem(nodeIndex).getAcceptState();
}
/**
* Returns the NFA node at the specified index
*
* @param index
* The index of the node to retrieve
* @return The NFA node at the specified index
*/
public NFANode getItem(int index)
{
return nodes[index];
}
/**
* Returns the index of the node that is the exit point for this NFA graph
*
* @return The NFA graph's exit node index
*/
public int getEnd()
{
return _end;
}
/**
* Returns the set of all input characters referenced by this NFA graph
*
* @return A CharacterSet of characters used by this NFA graph
*/
public CharacterSet getInputSet()
{
CharacterSet result = new CharacterSet();
for (int i = 0; i < nodes.length; i++)
{
Input input = this.getItem(i).getInput();
if (input != null)
{
result.addMembers(input.getCharacters());
}
}
return result;
}
/**
* Returns the index of the node that is the entry point for this NFA graph
*
* @return The NFA graph's entry node index
*/
public int getStart()
{
return _start;
}
/*
* Constructors
*/
/**
* Create a new instance of NFAGraph
*
* @param acceptState
* The accept state associated with this node
*/
public NFAGraph(int acceptState)
{
// create a new node and set the starting and ending points of this NFA
// graph to point to that node
this._start = this.createNewState();
this._end = this._start;
// set the initial node state to the specified AcceptState
this.getItem(this._end).setAcceptState(acceptState);
}
/*
* Methods
*/
/**
* Add a new node to the end of this NFA graph
*
* @param input
* The set of valid transitions to add to this node
*/
public void add(Input input)
{
// get current end node to copy its properties later
NFANode previousEnd = this.getItem(this._end);
// create a new end node
this._end = this.createNewState();
// move accept state to new end node
this.getItem(this._end).setAcceptState(previousEnd.getAcceptState());
// set inputs that fire a transition and set new state
previousEnd.setInput(input);
previousEnd.setNext(this._end);
// clear old end's accept state
previousEnd.setAcceptState(-1);
}
/**
* Join this NFA graph with the specified graph using an And operation
*
* @param rhs
* The NFA graph to And to this graph
*/
public void andMachines(NFAGraph rhs)
{
this.getItem(this._end).copy(rhs.getItem(rhs._start));
rhs.recycleNode(rhs._start);
rhs._start = this._end;
this._end = rhs._end;
}
/**
* Add the specified closure type to this NFA graph
*
* @param type
*/
private void applyClosure(int type)
{
int front = this.createNewState();
int back = this.createNewState();
this.getItem(front).addEpsilon(this._start);
if (type == Closure.KLEENE || type == Closure.OPTION)
{
this.getItem(front).addEpsilon(back);
}
if (type == Closure.KLEENE || type == Closure.POSITIVE)
{
this.getItem(this._end).addEpsilon(this._start);
}
// copy previous last state's AcceptState to the new last state
this.getItem(back).setAcceptState(this.getItem(this._end).getAcceptState());
// connect previous last state to new last state
this.getItem(this._end).addEpsilon(back);
// clear previous last state's AcceptState
this.getItem(this._end).setAcceptState(-1);
// update pointers to the beginning and end of this machine
this._start = front;
this._end = back;
}
/**
* Create a new node.
*
* @return The index to the newly created node
*/
public int createNewState()
{
int result;
if (recycle.size() > 0)
{
result = recycle.pop().intValue();
}
else
{
result = nodes.length;
// add new node
NFANode[] newNodes = new NFANode[result + 1];
System.arraycopy(nodes, 0, newNodes, 0, result);
newNodes[result] = new NFANode();
// assign new array
nodes = newNodes;
}
return result;
}
/**
* Create a Kleene closure around this NFA graph
*/
public void kleeneClosure()
{
this.applyClosure(Closure.KLEENE);
}
/**
* Create a Option around this NFA graph
*/
public void option()
{
this.applyClosure(Closure.OPTION);
}
/**
* Join this NFA graph with the specified graph using an Or operation
*
* @param rhs
* The NFA graph to Or to this graph
*/
public void orMachines(NFAGraph rhs)
{
int front = this.createNewState();
int back = this.createNewState();
// connect front to the starts of each machine
this.getItem(front).addEpsilon(this._start);
this.getItem(front).addEpsilon(rhs._start);
// // make sure both machines are for the same AcceptState
// if (this.getAcceptState(this._end) != rhs.getAcceptState(rhs._end))
// {
// // String msg = "Cannot 'or' two machines with differing accept
// // states";
// // throw new Exception(msg);
// }
// copy AcceptState from previous last state
this.getItem(back).setAcceptState(this.getItem(this._end).getAcceptState());
// connect ends of each machine to new last state
this.getItem(this._end).addEpsilon(back);
rhs.getItem(rhs._end).addEpsilon(back);
// clear previous AcceptStates
this.getItem(this._end).setAcceptState(-1);
rhs.getItem(rhs._end).setAcceptState(-1);
// update pointers to the start and end of this machine
this._start = front;
this._end = back;
}
/**
* Create a Positive closure around this NFA graph
*/
public void positiveClosure()
{
this.applyClosure(Closure.POSITIVE);
}
/**
* Add the specified node into the recycle bin
*
* @param index
* The index of the node to recycle
*/
public void recycleNode(int index)
{
this.getItem(index).reset();
recycle.push(new Integer(index));
}
/**
* Globally reset the NFA machine
*/
public static void reset()
{
nodes = new NFANode[0];
recycle.clear();
}
/**
* Return a string representation of this NFA graph
*
* @return Returns a string representation of this NFA Graph
*/
public String toString()
{
StringBuffer sb = new StringBuffer();
sb.append("NFA\n===\n"); //$NON-NLS-1$
for (int i = 0; i < nodes.length; i++)
{
if (i == this._start && i == this._end)
{
sb.append("<->").append(i).append(" : "); //$NON-NLS-1$ //$NON-NLS-2$
}
else if (i == this._start)
{
sb.append(" ->").append(i).append(" : "); //$NON-NLS-1$ //$NON-NLS-2$
}
else if (i == this._end)
{
sb.append("<- ").append(i).append(" : "); //$NON-NLS-1$ //$NON-NLS-2$
}
else
{
sb.append(" ").append(i).append(" : "); //$NON-NLS-1$ //$NON-NLS-2$
}
// output state
sb.append(this.getItem(i));
// epsilon reachable
sb.append("\n"); //$NON-NLS-1$
}
// show the input characters
sb.append("\nInputs\n======\n").append(this.getInputSet()).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
return sb.toString();
}
}