package de.unisiegen.gtitool.ui.utils;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Stack;
import org.jgraph.graph.DefaultGraphModel;
import de.unisiegen.gtitool.core.entities.State;
import de.unisiegen.gtitool.core.entities.Symbol;
import de.unisiegen.gtitool.core.entities.Transition;
import de.unisiegen.gtitool.core.machines.Machine;
import de.unisiegen.gtitool.core.parser.style.PrettyString;
import de.unisiegen.gtitool.core.parser.style.PrettyToken;
import de.unisiegen.gtitool.core.parser.style.Style;
import de.unisiegen.gtitool.ui.i18n.Messages;
import de.unisiegen.gtitool.ui.jgraph.DefaultStateView;
import de.unisiegen.gtitool.ui.logic.MinimizeMachineDialog;
import de.unisiegen.gtitool.ui.model.DefaultMachineModel;
import de.unisiegen.gtitool.ui.redoundo.MultiItem;
import de.unisiegen.gtitool.ui.redoundo.RedoUndoItem;
/**
* Minimize a given {@link Machine}.
*
* @author Benjamin Mies
* @version $Id$
*/
public class Minimizer
{
/**
* This class represents a minimize item. The is on item per minimize step.
*/
public class MinimizeItem
{
/**
* The {@link DefaultStateView} groups.
*/
public ArrayList < ArrayList < DefaultStateView > > groups;
/**
* The {@link PrettyString}.
*/
public PrettyString prettyString;
/**
* The {@link RedoUndoItem}.
*/
public RedoUndoItem redoUndoItem;
/**
* List of the {@link Transition}s.
*/
public ArrayList < Transition > transitionList;
/**
* Allocate a new {@link MinimizeItem}.
*
* @param groups The actual groups for this step.
* @param prettyString The {@link PrettyString} for this row.
* @param transitionList List of the {@link Transition}s.
* @param redoUndoItem The {@link RedoUndoItem}.
*/
public MinimizeItem ( ArrayList < ArrayList < DefaultStateView > > groups,
PrettyString prettyString, ArrayList < Transition > transitionList,
RedoUndoItem redoUndoItem )
{
this.groups = groups;
this.prettyString = prettyString;
this.transitionList = transitionList;
this.redoUndoItem = redoUndoItem;
}
}
/**
* The {@link DefaultStateView} groups.
*/
private ArrayList < ArrayList < DefaultStateView > > activeGroups = new ArrayList < ArrayList < DefaultStateView > > ();
/**
* The active {@link MinimizeItem}.
*/
private MinimizeItem activeMinimizeItem;
/**
* Flag indicates if begin reached.
*/
private boolean begin = true;
/**
* The predefined {@link Color}s of the groups.
*/
Color [] colors = new Color []
{ Color.blue, Color.cyan, Color.lightGray, Color.green, Color.magenta,
Color.yellow, Color.orange, Color.pink, Color.white, Color.darkGray };
/**
* The {@link MinimizeMachineDialog}.
*/
private MinimizeMachineDialog dialog;
/**
* Flag indicates if minimization operation finished.
*/
private boolean finished = false;
/**
* The {@link DefaultMachineModel}.
*/
private DefaultMachineModel model;
/**
* The states for a new group.
*/
private ArrayList < DefaultStateView > newGroupStates = new ArrayList < DefaultStateView > ();
/**
* The old groups needed for previous step.
*/
private Stack < MinimizeItem > nextStep = new Stack < MinimizeItem > ();
/**
* The not reachable states.
*/
private ArrayList < DefaultStateView > notReachable = new ArrayList < DefaultStateView > ();
/**
* The old groups needed for previous step.
*/
private Stack < MinimizeItem > previousSteps = new Stack < MinimizeItem > ();
/**
* List of the {@link Transition}s.
*/
private ArrayList < Transition > transitions = new ArrayList < Transition > ();
/**
* Allocate a new {@link Minimizer}.
*
* @param model The {@link DefaultMachineModel}.
* @param dialog The {@link MinimizeMachineDialog}.
*/
public Minimizer ( DefaultMachineModel model, MinimizeMachineDialog dialog )
{
this.model = model;
this.dialog = dialog;
}
/**
* Cretate the initial groups.
*/
private void createInitialGroups ()
{
ArrayList < DefaultStateView > notFinalStates = new ArrayList < DefaultStateView > ();
ArrayList < DefaultStateView > finalStates = new ArrayList < DefaultStateView > ();
for ( DefaultStateView current : this.model.getStateViewList () )
{
if ( current.getState ().isFinalState () )
{
if ( !this.notReachable.contains ( current ) )
{
finalStates.add ( current );
}
}
else
{
if ( !this.notReachable.contains ( current ) )
{
notFinalStates.add ( current );
}
}
}
if ( notFinalStates.size () > 0 )
{
this.activeGroups.add ( notFinalStates );
}
if ( finalStates.size () > 0 )
{
this.activeGroups.add ( finalStates );
}
}
/**
* Create a new {@link PrettyString}.
*
* @param states the list of the {@link DefaultStateView}s.
* @param oldGroup The old group of the removed states.
* @return The created {@link PrettyString}.
*/
private PrettyString createPrettyString (
ArrayList < DefaultStateView > states,
ArrayList < DefaultStateView > oldGroup )
{
PrettyString prettyString = new PrettyString ();
for ( int i = 0 ; i < states.size () ; i++ )
{
if ( ( i != 0 ) && ( i < ( states.size () - 1 ) ) )
{
prettyString.add ( new PrettyToken ( ", ", Style.NONE ) ); //$NON-NLS-1$>
}
if ( ( i != 0 ) && ( i == ( states.size () - 1 ) ) )
{
prettyString.add ( new PrettyToken ( " " + Messages //$NON-NLS-1$
.getString ( "And" ) + " ", Style.NONE ) ); //$NON-NLS-1$ //$NON-NLS-2$>
}
prettyString.add ( states.get ( i ).getState () );
}
prettyString.add ( new PrettyToken ( " " + Messages //$NON-NLS-1$
.getString ( "MinimizeMachineDialog.PrettyString" ) //$NON-NLS-1$
+ " ", Style.NONE ) ); //$NON-NLS-1$
for ( int i = 0 ; i < oldGroup.size () ; i++ )
{
if ( ( i != 0 ) && ( i < ( oldGroup.size () - 1 ) ) )
{
prettyString.add ( new PrettyToken ( ", ", Style.NONE ) ); //$NON-NLS-1$>
}
if ( ( i != 0 ) && ( i == ( oldGroup.size () - 1 ) ) )
{
prettyString.add ( new PrettyToken ( " " + Messages //$NON-NLS-1$
.getString ( "And" ) + " ", Style.NONE ) ); //$NON-NLS-1$ //$NON-NLS-2$>
}
prettyString.add ( oldGroup.get ( i ).getState () );
}
return prettyString;
}
/**
* Returns the group for a given {@link State}.
*
* @param state The {@link State}
* @return The group containing the {@link State}
*/
private ArrayList < DefaultStateView > getGroupForState ( State state )
{
for ( ArrayList < DefaultStateView > currentGroup : this.activeGroups )
{
for ( DefaultStateView current : currentGroup )
{
if ( current.getState ().equals ( state ) )
{
return currentGroup;
}
}
}
return null;
}
/**
* Returns the groups.
*
* @return The groups.
* @see #activeGroups
*/
public ArrayList < ArrayList < DefaultStateView >> getGroups ()
{
return this.activeGroups;
}
/**
* Highlight the groups
*/
private void highlightGroups ()
{
if ( this.activeGroups.size () == 1 )
{
return;
}
for ( ArrayList < DefaultStateView > group : this.activeGroups )
{
Color color = null;
boolean notReachableGroup = true;
for ( DefaultStateView current : group )
{
notReachableGroup = this.notReachable.contains ( current );
}
if ( notReachableGroup )
{
color = Color.white;
}
else
{
int index = ( this.activeGroups.indexOf ( group ) ) % 10;
color = this.colors [ index ];
}
if ( group.containsAll ( this.notReachable ) )
{
for ( DefaultStateView current : group )
{
current.setOverwrittenColor ( Color.white );
}
}
for ( DefaultStateView current : group )
{
current.setOverwrittenColor ( color );
}
}
this.model.getGraphModel ().cellsChanged (
DefaultGraphModel.getAll ( this.model.getGraphModel () ) );
}
/**
* Minimize the given {@link Machine}.
*/
public void initialize ()
{
ArrayList < ArrayList < DefaultStateView > > oldGroups = new ArrayList < ArrayList < DefaultStateView > > ();
ArrayList < DefaultStateView > tmpList = new ArrayList < DefaultStateView > ();
tmpList.addAll ( this.model.getStateViewList () );
oldGroups.add ( tmpList );
this.activeMinimizeItem = new MinimizeItem ( oldGroups, null,
new ArrayList < Transition > (), null );
this.previousSteps.push ( this.activeMinimizeItem );
oldGroups = new ArrayList < ArrayList < DefaultStateView > > ();
tmpList = new ArrayList < DefaultStateView > ();
tmpList.addAll ( this.model.getStateViewList () );
for ( State current : this.model.getMachine ().getNotReachableStates () )
{
DefaultStateView defaultStateView = this.model.getStateById ( current
.getId () );
tmpList.remove ( defaultStateView );
this.notReachable.add ( defaultStateView );
}
oldGroups.add ( tmpList );
oldGroups.add ( this.notReachable );
PrettyString prettyString = new PrettyString ();
if ( this.notReachable.size () > 0 )
{
prettyString.add ( new PrettyToken ( Messages
.getString ( "MinimizeMachineDialog.PrettyStringNotReachable" ) //$NON-NLS-1$
+ " ", Style.NONE ) ); //$NON-NLS-1$
for ( int i = 0 ; i < this.notReachable.size () ; i++ )
{
if ( ( i != 0 ) && ( i < ( this.notReachable.size () - 1 ) ) )
{
prettyString.add ( new PrettyToken ( ", ", Style.NONE ) ); //$NON-NLS-1$>
}
if ( ( i != 0 ) && ( i == ( this.notReachable.size () - 1 ) ) )
{
prettyString.add ( new PrettyToken ( " " + Messages //$NON-NLS-1$
.getString ( "And" ) + " ", Style.NONE ) ); //$NON-NLS-1$ //$NON-NLS-2$>
}
prettyString.add ( this.notReachable.get ( i ).getState () );
}
}
else
{
prettyString.add ( new PrettyToken ( Messages
.getString ( "MinimizeMachineDialog.PrettyStringAllReachable" ) //$NON-NLS-1$
, Style.NONE ) );
}
MultiItem item = new MultiItem ();
for ( DefaultStateView current : this.notReachable )
{
item.addItem ( this.model.removeState ( current, false ) );
}
item.undo ();
this.activeMinimizeItem = new MinimizeItem ( oldGroups, prettyString,
new ArrayList < Transition > (), item );
this.previousSteps.push ( this.activeMinimizeItem );
createInitialGroups ();
oldGroups = new ArrayList < ArrayList < DefaultStateView > > ();
for ( ArrayList < DefaultStateView > current : this.activeGroups )
{
ArrayList < DefaultStateView > tmpGroup = new ArrayList < DefaultStateView > ();
tmpGroup.addAll ( current );
oldGroups.add ( tmpGroup );
}
prettyString = new PrettyString ();
if ( this.activeGroups.size () > 1 )
{
prettyString.add ( new PrettyToken ( Messages
.getString ( "MinimizeMachineDialog.PrettyStringFinalStates" ) //$NON-NLS-1$
+ " ", Style.NONE ) ); //$NON-NLS-1$
ArrayList < DefaultStateView > states = new ArrayList < DefaultStateView > ();
states.addAll ( this.activeGroups.get ( 1 ) );
for ( int i = 0 ; i < states.size () ; i++ )
{
if ( ( i != 0 ) && ( i < ( states.size () - 1 ) ) )
{
prettyString.add ( new PrettyToken ( ", ", Style.NONE ) ); //$NON-NLS-1$>
}
if ( ( i != 0 ) && ( i == ( states.size () - 1 ) ) )
{
prettyString.add ( new PrettyToken ( " " + Messages //$NON-NLS-1$
.getString ( "And" ) + " ", Style.NONE ) ); //$NON-NLS-1$ //$NON-NLS-2$>
}
prettyString.add ( states.get ( i ).getState () );
}
}
else
{
prettyString.add ( new PrettyToken ( Messages
.getString ( "MinimizeMachineDialog.PrettyStringNoFinalStates" ) //$NON-NLS-1$
, Style.NONE ) );
}
this.activeMinimizeItem = new MinimizeItem ( oldGroups, prettyString,
new ArrayList < Transition > (), null );
this.previousSteps.push ( this.activeMinimizeItem );
minimize ();
// Add a final step to show the new machine.
this.previousSteps.push ( new MinimizeItem ( getGroups (), Messages
.getPrettyString ( "MinimizeMachineDialog.FinalPrettyString" ), //$NON-NLS-1$
new ArrayList < Transition > (), null ) );
while ( this.previousSteps.size () > 1 )
{
this.nextStep.push ( this.previousSteps.pop () );
}
this.activeMinimizeItem = this.previousSteps.pop ();
this.activeGroups = this.activeMinimizeItem.groups;
highlightGroups ();
}
/**
* Returns the begin.
*
* @return The begin.
* @see #begin
*/
public boolean isBegin ()
{
return this.begin;
}
/**
* Returns true if minimization operation finished.
*
* @return true if minimization operation finished.
*/
public boolean isFinished ()
{
return this.finished;
}
/**
* Returns true if given {@link State} is in this group
*
* @param group The group of {@link DefaultStateView}s
* @param state The {@link State}
* @return true if given {@link State} is in this group, else false
*/
private boolean isInGroup ( ArrayList < DefaultStateView > group, State state )
{
for ( DefaultStateView current : group )
{
if ( current.getState ().equals ( state ) )
{
return true;
}
}
return false;
}
/**
* The internal minimize operation.
*/
private void minimize ()
{
ArrayList < DefaultStateView > currentGroup = null;
for ( ArrayList < DefaultStateView > current : this.activeGroups )
{
currentGroup = current;
if ( current.size () > 1 )
{
tryToSplitGroup ( current );
if ( this.newGroupStates.size () > 0 )
{
break;
}
}
}
if ( this.newGroupStates.size () > 0 )
{
this.activeGroups.add ( this.newGroupStates );
ArrayList < ArrayList < DefaultStateView > > oldGroup = new ArrayList < ArrayList < DefaultStateView > > ();
for ( ArrayList < DefaultStateView > group : this.activeGroups )
{
ArrayList < DefaultStateView > tmpGroup = new ArrayList < DefaultStateView > ();
tmpGroup.addAll ( group );
oldGroup.add ( tmpGroup );
}
ArrayList < Transition > transitionList = new ArrayList < Transition > ();
transitionList.addAll ( this.transitions );
this.previousSteps.push ( new MinimizeItem ( oldGroup,
createPrettyString ( this.newGroupStates, currentGroup ),
transitionList, null ) );
this.newGroupStates = new ArrayList < DefaultStateView > ();
minimize ();
return;
}
}
/**
* Handle next step of the minimization.
*/
public void nextStep ()
{
this.previousSteps.push ( this.activeMinimizeItem );
this.activeMinimizeItem = this.nextStep.pop ();
this.activeGroups = this.activeMinimizeItem.groups;
if ( this.activeMinimizeItem.redoUndoItem != null )
{
this.activeMinimizeItem.redoUndoItem.redo ();
}
highlightGroups ();
this.begin = false;
this.finished = this.nextStep.isEmpty ();
this.dialog.addOutlineComment ( this.activeMinimizeItem.prettyString,
this.activeMinimizeItem.transitionList );
}
/**
* Handle previous step of the minimization.
*/
public void previousStep ()
{
if ( this.activeMinimizeItem.redoUndoItem != null )
{
this.activeMinimizeItem.redoUndoItem.undo ();
}
this.nextStep.push ( this.activeMinimizeItem );
this.activeMinimizeItem = this.previousSteps.pop ();
this.activeGroups = this.activeMinimizeItem.groups;
highlightGroups ();
this.begin = this.previousSteps.isEmpty ();
this.finished = false;
}
/**
* Try to split the actual group.
*
* @param group The actual group.
*/
private void tryToSplitGroup ( ArrayList < DefaultStateView > group )
{
ArrayList < DefaultStateView > targetGroup = null;
this.transitions.clear ();
for ( Symbol symbol : this.model.getMachine ().getAlphabet () )
{
loop : for ( DefaultStateView defaultStateView : group )
{
for ( Transition transition : defaultStateView.getState ()
.getTransitionBegin () )
{
if ( transition.getSymbol ().contains ( symbol ) )
{
if ( targetGroup == null )
{
targetGroup = getGroupForState ( transition.getStateEnd () );
continue loop;
}
if ( !isInGroup ( targetGroup, transition.getStateEnd () ) )
{
this.newGroupStates.remove ( defaultStateView );
this.newGroupStates.add ( defaultStateView );
this.transitions.add ( transition );
}
}
}
}
targetGroup = null;
}
if ( this.newGroupStates.size () == group.size () )
{
this.newGroupStates.clear ();
}
group.removeAll ( this.newGroupStates );
}
}