package de.unisiegen.gtitool.ui.model;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import org.jgraph.graph.DefaultGraphModel;
import org.jgraph.graph.EdgeView;
import org.jgraph.graph.GraphConstants;
import de.unisiegen.gtitool.core.entities.DefaultRegexAlphabet;
import de.unisiegen.gtitool.core.entities.regex.ConcatenationNode;
import de.unisiegen.gtitool.core.entities.regex.DisjunctionNode;
import de.unisiegen.gtitool.core.entities.regex.KleeneNode;
import de.unisiegen.gtitool.core.entities.regex.LeafNode;
import de.unisiegen.gtitool.core.entities.regex.OptionalNode;
import de.unisiegen.gtitool.core.entities.regex.PlusNode;
import de.unisiegen.gtitool.core.entities.regex.Regex;
import de.unisiegen.gtitool.core.entities.regex.RegexNode;
import de.unisiegen.gtitool.core.exceptions.alphabet.AlphabetException;
import de.unisiegen.gtitool.core.parser.regex.RegexParseable;
import de.unisiegen.gtitool.core.regex.DefaultRegex;
import de.unisiegen.gtitool.core.storage.Attribute;
import de.unisiegen.gtitool.core.storage.Element;
import de.unisiegen.gtitool.core.storage.Storable;
import de.unisiegen.gtitool.core.storage.exceptions.StoreException;
import de.unisiegen.gtitool.ui.i18n.Messages;
import de.unisiegen.gtitool.ui.jgraph.DefaultNodeView;
import de.unisiegen.gtitool.ui.jgraph.DefaultRegexEdgeView;
import de.unisiegen.gtitool.ui.jgraph.EdgeRenderer;
import de.unisiegen.gtitool.ui.jgraph.GPCellViewFactory;
import de.unisiegen.gtitool.ui.jgraph.JGTIGraph;
import de.unisiegen.gtitool.ui.jgraph.JGraphpadParallelSplineRouter;
import de.unisiegen.gtitool.ui.jgraph.NodeView;
/**
* The Default model for a Regex
*/
public class DefaultRegexModel implements DefaultModel, Storable
{
/**
* The default y-space in the graph
*/
private int Y_SPACE = 50;
/**
* The default x-space in the graph
*/
private int X_SPACE = 70;
/**
* The default x-border of the graph
*/
private final int X_BORDER = 15;
/**
* The {@link DefaultRegex}
*/
private DefaultRegex regex;
/**
* The {@link JGTIGraph}
*/
private JGTIGraph jGTIGraph;
/**
* A list of all {@link DefaultNodeView}s
*/
private ArrayList < DefaultNodeView > nodeViewList = new ArrayList < DefaultNodeView > ();
/**
* A list of all {@link DefaultRegexEdgeView}s
*/
private ArrayList < DefaultRegexEdgeView > regexEdgeViewList = new ArrayList < DefaultRegexEdgeView > ();
/**
* The {@link DefaultGraphModel} for this model.
*/
private DefaultGraphModel graphModel;
/**
* The {@link Regex} version.
*/
private static final int REGEX_VERSION = 1;
/**
* The initial regex string
*/
private String initialRegexString;
/**
* The actualt RegexString. Used for saving
*/
private String actualRegexString;
/**
* The {@link FontMetrics}
*/
private FontMetrics metrics = null;
/**
* The x overhead
*/
private int x_moving = 0;
/**
* Constructor for a new {@link DefaultRegexModel}
*
* @param regex The {@link DefaultRegex} for this model
*/
public DefaultRegexModel ( DefaultRegex regex )
{
this.regex = regex;
this.initialRegexString = null;
}
/**
* Constructor for a saved {@link DefaultRegexModel}
*
* @param element The saved {@link DefaultRegex}
* @param newFile True if a new file is created
* @throws StoreException
* @throws AlphabetException
*/
public DefaultRegexModel ( Element element, boolean newFile )
throws StoreException, AlphabetException
{
boolean foundVersion = false;
String regexString = new String ();
for ( Attribute attr : element.getAttribute () )
{
if ( attr.getName ().equals ( "regexVersion" ) ) //$NON-NLS-1$
{
foundVersion = true;
if ( attr.getValueInt () != REGEX_VERSION )
{
throw new StoreException ( de.unisiegen.gtitool.core.i18n.Messages
.getString ( "StoreException.IncompatibleVersion" ) ); //$NON-NLS-1$
}
}
if ( attr.getName ().equals ( "regexString" ) ) //$NON-NLS-1$
{
regexString = attr.getValue ();
if ( ( regexString != null ) && regexString.equals ( " " ) ) //$NON-NLS-1$
{
regexString = new String ();
}
}
}
if ( regexString == null )
{
throw new StoreException ( de.unisiegen.gtitool.core.i18n.Messages
.getString ( "StoreException.AdditionalAttribute" ) ); //$NON-NLS-1$
}
if ( newFile )
{
this.initialRegexString = null;
}
else
{
this.initialRegexString = regexString;
}
DefaultRegexAlphabet da = new DefaultRegexAlphabet ( element
.getElement ( 0 ) );
this.regex = new DefaultRegex ( da );
RegexParseable rp = new RegexParseable ();
try
{
this.regex.setRegexNode ( ( RegexNode ) rp.newParser ( regexString )
.parse (), regexString );
}
catch ( Exception exc )
{
// Nothing to do here. Regex was not legal as it has been saved.
}
if ( !foundVersion )
{
throw new StoreException ( de.unisiegen.gtitool.core.i18n.Messages
.getString ( "StoreException.AdditionalAttribute" ) ); //$NON-NLS-1$
}
}
/**
* Recursivly add the Nodes to the Model
*
* @param parent The parent {@link DefaultNodeView}
*/
private void addNodesToModel ( DefaultNodeView parent )
{
int y = parent.getY () + this.Y_SPACE;
int x = parent.getX ();
if ( parent.getNode ().getChildren ().size () > 1 )
{
for ( int i = 0 ; i < parent.getNode ().getChildren ().size () ; i++ )
{
RegexNode childNode = parent.getNode ().getChildren ().get ( i );
if ( i == 0 )
{
// Left Node
x -= ( this.X_SPACE / 2 );
if ( childNode instanceof ConcatenationNode )
{
ConcatenationNode con = ( ConcatenationNode ) childNode;
x -= ( ( this.X_SPACE / 2 ) * con.countRightChildren () );
}
else if ( childNode instanceof DisjunctionNode )
{
DisjunctionNode dis = ( DisjunctionNode ) childNode;
x -= ( ( this.X_SPACE / 2 ) * dis.countRightChildren () );
}
else if ( ( childNode instanceof KleeneNode )
|| ( childNode instanceof PlusNode )
|| ( childNode instanceof OptionalNode ) )
{
x -= ( ( this.X_SPACE / 2 ) * ( childNode.getRightChildrenCount () ) );
}
}
else
{
// Right node
x = parent.getX ();
x += ( this.X_SPACE / 2 );
if ( childNode instanceof ConcatenationNode )
{
ConcatenationNode con = ( ConcatenationNode ) childNode;
x += ( ( this.X_SPACE / 2 ) * con.countLeftChildren () );
}
else if ( childNode instanceof DisjunctionNode )
{
DisjunctionNode dis = ( DisjunctionNode ) childNode;
x += ( ( this.X_SPACE / 2 ) * dis.countLeftChildren () );
}
else if ( ( childNode instanceof KleeneNode )
|| ( childNode instanceof PlusNode )
|| ( childNode instanceof OptionalNode ) )
{
x += ( ( this.X_SPACE / 2 ) * ( childNode.getLeftChildrenCount () ) );
}
}
DefaultNodeView child = createNodeView ( x, y, childNode );
createRegexEdgeView ( parent, child );
addNodesToModel ( child );
}
}
else if ( parent.getNode ().getChildren ().size () == 1 )
{
DefaultNodeView child = createNodeView ( x, y, parent.getNode ()
.getChildren ().get ( 0 ) );
createRegexEdgeView ( parent, child );
addNodesToModel ( child );
}
}
/**
* Changes the RegexNode
*
* @param node The new {@link RegexNode}
* @param regexString The new String for the Regex
*/
public void changeRegexNode ( RegexNode node, String regexString )
{
this.regex.setRegexNode ( node, regexString );
}
/**
* Creates a new {@link DefaultNodeView}
*
* @param x The x value
* @param y The y value
* @param node The content
* @return The created {@link DefaultNodeView}
*/
@SuppressWarnings ( "unchecked" )
public DefaultNodeView createNodeView ( double x, double y, RegexNode node )
{
DefaultNodeView nodeView = new DefaultNodeView ( node, ( int ) x, ( int ) y );
String viewClass = NodeView.class.getName ();
int maxY = this.metrics.getHeight () + 10;
int maxX = 15;
if ( node instanceof LeafNode )
{
LeafNode leaf = ( LeafNode ) node;
if ( leaf.isPositionShown () )
{
maxY = this.metrics.getHeight () * 2 + 4;
maxX = Math.max ( 25, Math.max ( this.metrics.stringWidth ( node
.getNodeString ().toString () ), this.metrics.stringWidth ( String
.valueOf ( leaf.getPosition () ) ) ) );
// System.err.println ("Node: " + node.getNodeString () + " maxX: " +
// maxX);
}
else
{
maxX = Math.max ( 25, this.metrics.stringWidth ( node.getNodeString ()
.toString () ) );
}
}
nodeView.setHeight ( maxY );
nodeView.setWidth ( maxX );
// Set bounds
GraphConstants.setBounds ( nodeView.getAttributes (),
new Rectangle2D.Double ( x, y, maxX, maxY ) );
// set the view class (indirection for the renderer and the editor)
GPCellViewFactory.setViewClass ( nodeView.getAttributes (), viewClass );
// Opaque
GraphConstants.setOpaque ( nodeView.getAttributes (), true );
// Gradient
GraphConstants.setGradientColor ( nodeView.getAttributes (), Color.BLACK );
// Set black border
GraphConstants.setBorderColor ( nodeView.getAttributes (), Color.BLACK );
// Set the line width
GraphConstants.setLineWidth ( nodeView.getAttributes (), 1 );
// Add a floating port
nodeView.addPort ();
this.jGTIGraph.getGraphLayoutCache ().insert ( nodeView );
this.nodeViewList.add ( nodeView );
return nodeView;
}
/**
* Creates a new {@link DefaultRegexEdgeView}
*
* @param parent The parent {@link DefaultNodeView}
* @param child The child {@link DefaultNodeView}
* @return The created {@link DefaultRegexEdgeView}
*/
public DefaultRegexEdgeView createRegexEdgeView ( DefaultNodeView parent,
DefaultNodeView child )
{
DefaultRegexEdgeView edgeView = new DefaultRegexEdgeView ( parent, child );
// Set the parallel routing
JGraphpadParallelSplineRouter.getSharedInstance ().setEdgeSeparation ( 25 );
GraphConstants.setRouting ( edgeView.getAttributes (),
JGraphpadParallelSplineRouter.getSharedInstance () );
GraphConstants.setSelectable ( edgeView.getAttributes (), false );
this.jGTIGraph.getGraphLayoutCache ().insertEdge ( edgeView,
parent.getChildAt ( 0 ), child.getChildAt ( 0 ) );
this.regexEdgeViewList.add ( edgeView );
return edgeView;
}
/**
* Creates the Tree
*/
public void createTree ()
{
if ( ( this.regex == null ) || ( this.regex.getRegexNode () == null )
|| ( this.regex.getRegexNode ().toString ().length () == 0 ) )
{
return;
}
this.X_SPACE = 70;
this.Y_SPACE = 50;
this.regexEdgeViewList.clear ();
this.nodeViewList.clear ();
if ( this.regex.getRegexNode ().getWidth () > 18 )
{
this.X_SPACE -= 20;
}
if ( this.regex.getRegexNode ().getHeight () > 8 )
{
this.Y_SPACE -= 10;
}
DefaultNodeView parent = createNodeView ( 0, 0, this.regex.getRegexNode () );
addNodesToModel ( parent );
int x_overhead = 0;
for ( DefaultNodeView nodeView : getNodeViewList () )
{
int act_x = nodeView.getX ();
if ( ( act_x < 0 ) && ( Math.abs ( act_x ) > x_overhead ) )
{
x_overhead = Math.abs ( act_x );
}
}
this.x_moving = x_overhead / ( this.X_SPACE / 2 );
if ( x_overhead > 0 )
{
x_overhead += this.X_BORDER;
for ( DefaultNodeView nodeView : getNodeViewList () )
{
nodeView.moveRelative ( x_overhead, 0 );
}
getGraphModel ().cellsChanged (
DefaultGraphModel.getAll ( getGraphModel () ) );
}
}
/**
* {@inheritDoc}
*
* @see DefaultModel#getElement()
*/
public Element getElement ()
{
Element newElement = new Element ( "RegexModel" ); //$NON-NLS-1$
newElement.addAttribute ( new Attribute ( "regexVersion", //$NON-NLS-1$
REGEX_VERSION ) );
newElement.addElement ( this.regex.getAlphabet () );
if ( ( this.actualRegexString == null )
|| ( this.actualRegexString.length () == 0 ) )
{
newElement.addAttribute ( new Attribute ( "regexString", //$NON-NLS-1$
" " ) ); //$NON-NLS-1$
}
else
{
newElement.addAttribute ( new Attribute ( "regexString", //$NON-NLS-1$
this.actualRegexString ) );
}
return newElement;
}
/**
* Returns the graphModel.
*
* @return The graphModel.
* @see #graphModel
*/
public DefaultGraphModel getGraphModel ()
{
return this.graphModel;
}
/**
* Returns the initialRegexString.
*
* @return The initialRegexString.
* @see #initialRegexString
*/
public String getInitialRegexString ()
{
return this.initialRegexString;
}
/**
* Returns the jGTIGraph.
*
* @return The jGTIGraph.
* @see #jGTIGraph
*/
public JGTIGraph getJGTIGraph ()
{
return this.jGTIGraph;
}
/**
* Returns the nodeViewList.
*
* @return The nodeViewList.
* @see #nodeViewList
*/
public ArrayList < DefaultNodeView > getNodeViewList ()
{
return this.nodeViewList;
}
/**
* Returns the regex.
*
* @return The regex.
* @see #regex
*/
public DefaultRegex getRegex ()
{
return this.regex;
}
/**
* Initializes the {@link JGTIGraph}
*/
public void initializeGraph ()
{
this.graphModel = new DefaultGraphModel ();
this.jGTIGraph = new JGTIGraph ( this.graphModel );
this.jGTIGraph.setDoubleBuffered ( false );
this.jGTIGraph.getGraphLayoutCache ()
.setFactory ( new GPCellViewFactory () );
this.jGTIGraph.setInvokesStopCellEditing ( true );
this.jGTIGraph.setJumpToDefaultPort ( true );
this.jGTIGraph.setSizeable ( false );
this.jGTIGraph.setConnectable ( false );
this.jGTIGraph.setDisconnectable ( false );
this.jGTIGraph.setEdgeLabelsMovable ( false );
this.jGTIGraph.setEditable ( false );
this.jGTIGraph.setHandleSize ( 0 );
this.jGTIGraph.setXorEnabled ( false );
this.metrics = getJGTIGraph ().getFontMetrics ( this.jGTIGraph.getFont () );
EdgeView.renderer = new EdgeRenderer ();
EdgeView.renderer.setForeground ( Color.BLACK );
}
/**
* Sets the actualRegexString.
*
* @param actualRegexString The actualRegexString to set.
* @see #actualRegexString
*/
public void setActualRegexString ( String actualRegexString )
{
this.actualRegexString = actualRegexString;
}
/**
* Sets the initialRegexString.
*
* @param initialRegexString The initialRegexString to set.
* @see #initialRegexString
*/
public void setInitialRegexString ( String initialRegexString )
{
this.initialRegexString = initialRegexString;
}
/**
* Converts the graph to Latex
*
* @return The latex string
*/
public String toLatexString ()
{
String s = ""; //$NON-NLS-1$
RegexNode node = this.regex.getRegexNode ();
if ( node == null )
{
return s;
}
int w = node.getWidth ();
s += " %" + Messages.getString ( "LatexComment.CreateTabular" ) + "\n"; //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
s += " \\begin{tabular}{"; //$NON-NLS-1$
for ( int i = 0 ; i < w ; i++ )
{
s += "c"; //$NON-NLS-1$
}
s += "}\n"; //$NON-NLS-1$
ArrayList < DefaultNodeView > nodes = this.nodeViewList;
Collections.sort ( nodes );
int j = 0;
s += " %" + Messages.getString ( "LatexComment.CreateNodes" ) + "\n"; //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
for ( int i = 0 ; i < nodes.size () ; i++ )
{
DefaultNodeView view = nodes.get ( i );
int x_over = ( view.getX () ) / ( this.X_SPACE / 2 );
// Add space
s += " "; //$NON-NLS-1$
x_over += this.x_moving;
for ( ; j < x_over ; j++ )
{
s += " & "; //$NON-NLS-1$
}
String name = view.getNode ().getNodeString ().toString ();
if ( name.equals ( "#" ) ) //$NON-NLS-1$
{
name = "\\#"; //$NON-NLS-1$
}
else if ( name.equals ( "|" ) ) //$NON-NLS-1$
{
name = "$|$"; //$NON-NLS-1$
}
else if ( name.equals ( "\u03B5" ) ) //$NON-NLS-1$
{
name = "$\\epsilon$"; //$NON-NLS-1$
}
else if ( name.equals ( "ยท" ) ) { //$NON-NLS-1$
name = "$\\cdot$"; //$NON-NLS-1$
}
s += "\\node{r" + i + "}{" + name + "}"; //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
if ( !view.equals ( nodes.get ( nodes.size () - 1 ) )
&& ( view.getY () != nodes.get ( i + 1 ).getY () ) )
{
s += "\\\\[4ex]\n"; //$NON-NLS-1$
j = 0;
}
}
s += "\n \\end{tabular}\n"; //$NON-NLS-1$
s += " %" + Messages.getString ( "LatexComment.NodeConnect" ) + "\n"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
for ( DefaultRegexEdgeView v : this.regexEdgeViewList )
{
int parentId = nodes.indexOf ( v.getParentView () );
int childId = nodes.indexOf ( v.getChildView () );
s += " \\nodeconnect{r" + parentId + "}{r" + childId + "}\n"; //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$
}
return s;
}
}