package de.unisiegen.gtitool.core.grammars;
import java.util.ArrayList;
import java.util.TreeSet;
import javax.swing.event.EventListenerList;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
import de.unisiegen.gtitool.core.entities.DefaultNonterminalSymbol;
import de.unisiegen.gtitool.core.entities.NonterminalSymbol;
import de.unisiegen.gtitool.core.entities.NonterminalSymbolSet;
import de.unisiegen.gtitool.core.entities.Production;
import de.unisiegen.gtitool.core.entities.ProductionWordMember;
import de.unisiegen.gtitool.core.entities.TerminalSymbol;
import de.unisiegen.gtitool.core.entities.TerminalSymbolSet;
import de.unisiegen.gtitool.core.entities.listener.ModifyStatusChangedListener;
import de.unisiegen.gtitool.core.exceptions.grammar.GrammarDuplicateProductionException;
import de.unisiegen.gtitool.core.exceptions.grammar.GrammarException;
import de.unisiegen.gtitool.core.exceptions.grammar.GrammarNonterminalNotReachableException;
import de.unisiegen.gtitool.core.exceptions.grammar.GrammarRegularGrammarException;
import de.unisiegen.gtitool.core.exceptions.grammar.GrammarValidationException;
import de.unisiegen.gtitool.core.machines.AbstractMachine;
import de.unisiegen.gtitool.core.machines.Machine;
import de.unisiegen.gtitool.core.storage.Modifyable;
/**
* The abstract class for all grammars.
*
* @author Christian Fehler
* @version $Id$
*/
public abstract class AbstractGrammar implements Grammar
{
/**
* The serial version uid.
*/
private static final long serialVersionUID = -445079972963576721L;
/**
* List of listeners
*/
private EventListenerList listenerList = new EventListenerList ();
/**
* The {@link NonterminalSymbolSet}.
*/
private NonterminalSymbolSet nonterminalSymbolSet;
/**
* The {@link TerminalSymbolSet}.
*/
private TerminalSymbolSet terminalSymbolSet;
/**
* List containing all {@link Production}s.
*/
private ArrayList < Production > productions = new ArrayList < Production > ();
/**
* List containing all {@link Production}s until last save.
*/
private ArrayList < Production > initialProductions = new ArrayList < Production > ();
/**
* The start {@link NonterminalSymbol}.
*/
private NonterminalSymbol initialStartNonterminalSymbol = null;
/**
* The {@link ModifyStatusChangedListener}.
*/
private ModifyStatusChangedListener modifyStatusChangedListener;
/**
* The validation element list.
*/
private ArrayList < ValidationElement > validationElementList;
/**
* The start symbol of this grammar.
*/
private NonterminalSymbol startSymbol;
/**
* Allocate a new {@link AbstractGrammar}.
*
* @param nonterminalSymbolSet The {@link NonterminalSymbolSet}.
* @param terminalSymbolSet The {@link TerminalSymbolSet}.
* @param startSymbol The start symbol of this grammar.
* @param validationElements The validation elements which indicates which
* validation elements should be checked during a validation.
*/
public AbstractGrammar ( NonterminalSymbolSet nonterminalSymbolSet,
TerminalSymbolSet terminalSymbolSet, NonterminalSymbol startSymbol,
ValidationElement ... validationElements )
{
this.nonterminalSymbolSet = nonterminalSymbolSet;
this.terminalSymbolSet = terminalSymbolSet;
this.startSymbol = startSymbol;
this.modifyStatusChangedListener = new ModifyStatusChangedListener ()
{
public void modifyStatusChanged ( boolean modified )
{
fireModifyStatusChanged ( modified );
}
};
this.nonterminalSymbolSet
.addModifyStatusChangedListener ( this.modifyStatusChangedListener );
this.terminalSymbolSet
.addModifyStatusChangedListener ( this.modifyStatusChangedListener );
// validation elements
if ( validationElements == null )
{
throw new NullPointerException ( "validation elements is null" ); //$NON-NLS-1$
}
this.validationElementList = new ArrayList < ValidationElement > ();
for ( ValidationElement current : validationElements )
{
this.validationElementList.add ( current );
}
// reset modify
resetModify ();
}
/**
* {@inheritDoc}
*
* @see Machine#addModifyStatusChangedListener(ModifyStatusChangedListener)
*/
public final void addModifyStatusChangedListener (
ModifyStatusChangedListener listener )
{
this.listenerList.add ( ModifyStatusChangedListener.class, listener );
}
/**
* Add a {@link Production} to this grammar.
*
* @param production The {@link Production}.
*/
public void addProduction ( Production production )
{
this.productions.add ( production );
updateStartSymbol ();
production
.addModifyStatusChangedListener ( this.modifyStatusChangedListener );
fireModifyStatusChanged ( false );
}
/**
* {@inheritDoc}
*
* @see TableModel#addTableModelListener(TableModelListener)
*/
public final void addTableModelListener ( TableModelListener listener )
{
this.listenerList.add ( TableModelListener.class, listener );
}
/**
* Check the grammar for duplicate productions.
*
* @return list containing occured errors.
*/
private final ArrayList < GrammarException > checkDuplicateProduction ()
{
ArrayList < GrammarException > grammarExceptionList = new ArrayList < GrammarException > ();
ArrayList < Production > foundDuplicates = new ArrayList < Production > ();
for ( int i = 0 ; i < this.productions.size () ; i++ )
{
ArrayList < Production > duplicatedList = new ArrayList < Production > ();
for ( int j = i + 1 ; j < this.productions.size () ; j++ )
{
if ( !foundDuplicates.contains ( this.productions.get ( i ) )
&& this.productions.get ( i ).equals ( this.productions.get ( j ) ) )
{
duplicatedList.add ( this.productions.get ( j ) );
}
}
if ( duplicatedList.size () > 0 )
{
foundDuplicates.add ( this.productions.get ( i ) );
duplicatedList.add ( this.productions.get ( i ) );
grammarExceptionList.add ( new GrammarDuplicateProductionException (
duplicatedList ) );
}
}
return grammarExceptionList;
}
/**
* Check the grammar for not reachable nonterminal symbols.
*
* @return list containing occured errors.
*/
private final ArrayList < GrammarException > checkNonterminalNotReachable ()
{
ArrayList < GrammarException > grammarExceptionList = new ArrayList < GrammarException > ();
for ( NonterminalSymbol current : getNotReachableNonterminalSymbols () )
{
grammarExceptionList.add ( new GrammarNonterminalNotReachableException (
current ) );
}
return grammarExceptionList;
}
/**
* Check if the grammar is regular.
*
* @return list containing occured errors.
*/
private final ArrayList < GrammarException > checkRegularGrammar ()
{
ArrayList < GrammarException > grammarExceptionList = new ArrayList < GrammarException > ();
for ( Production current : this.productions )
{
ArrayList < ProductionWordMember > symbols = new ArrayList < ProductionWordMember > ();
ArrayList < ProductionWordMember > wordMemberList = new ArrayList < ProductionWordMember > ();
for ( ProductionWordMember wordMember : current.getProductionWord () )
{
wordMemberList.add ( wordMember );
}
// Epsilon
if ( wordMemberList.size () == 0 )
{
continue;
}
// One member and not a TerminalSymbol
if ( wordMemberList.size () == 1 )
{
if ( ! ( wordMemberList.get ( 0 ) instanceof TerminalSymbol ) )
{
symbols.add ( wordMemberList.get ( 0 ) );
grammarExceptionList.add ( new GrammarRegularGrammarException (
current, symbols ) );
}
}
// Two members and not a TerminalSymbol and a NonterminalSymbol
if ( wordMemberList.size () == 2 )
{
if ( ! ( wordMemberList.get ( 0 ) instanceof TerminalSymbol ) )
{
symbols.add ( wordMemberList.get ( 0 ) );
}
if ( ! ( wordMemberList.get ( 1 ) instanceof NonterminalSymbol ) )
{
symbols.add ( wordMemberList.get ( 1 ) );
}
if ( symbols.size () > 0 )
{
grammarExceptionList.add ( new GrammarRegularGrammarException (
current, symbols ) );
}
}
// More than two members
if ( wordMemberList.size () > 2 )
{
if ( ! ( wordMemberList.get ( 0 ) instanceof TerminalSymbol ) )
{
symbols.add ( wordMemberList.get ( 0 ) );
}
if ( ! ( wordMemberList.get ( 1 ) instanceof NonterminalSymbol ) )
{
symbols.add ( wordMemberList.get ( 1 ) );
}
for ( int i = 2 ; i < wordMemberList.size () ; i++ )
{
symbols.add ( wordMemberList.get ( i ) );
}
grammarExceptionList.add ( new GrammarRegularGrammarException (
current, symbols ) );
}
}
return grammarExceptionList;
}
/**
* Let the listeners know that the modify status has changed.
*
* @param forceModify True if the modify is forced, otherwise false.
*/
protected final void fireModifyStatusChanged ( boolean forceModify )
{
ModifyStatusChangedListener [] listeners = this.listenerList
.getListeners ( ModifyStatusChangedListener.class );
if ( forceModify )
{
for ( ModifyStatusChangedListener element : listeners )
{
element.modifyStatusChanged ( true );
}
}
else
{
boolean newModifyStatus = isModified ();
for ( ModifyStatusChangedListener element : listeners )
{
element.modifyStatusChanged ( newModifyStatus );
}
}
TableModelListener [] tableListeners = this.listenerList
.getListeners ( TableModelListener.class );
for ( TableModelListener l : tableListeners )
{
l.tableChanged ( new TableModelEvent ( this ) );
}
}
/**
* {@inheritDoc}
*
* @see javax.swing.table.TableModel#getColumnClass(int)
*/
public Class < ? > getColumnClass ( @SuppressWarnings ( "unused" )
int columnIndex )
{
return Production.class;
}
/**
* {@inheritDoc}
*
* @see javax.swing.table.TableModel#getColumnCount()
*/
public int getColumnCount ()
{
return 1;
}
/**
* {@inheritDoc}
*
* @see javax.swing.table.TableModel#getColumnName(int)
*/
public String getColumnName ( @SuppressWarnings ( "unused" )
int columnIndex )
{
return ""; //$NON-NLS-1$
}
/**
* Returns the {@link Grammar.GrammarType}.
*
* @return The {@link Grammar.GrammarType}.
*/
public abstract GrammarType getGrammarType ();
/**
* Returns the {@link NonterminalSymbolSet}.
*
* @return the {@link NonterminalSymbolSet}.
*/
public NonterminalSymbolSet getNonterminalSymbolSet ()
{
return this.nonterminalSymbolSet;
}
/**
* {@inheritDoc}
*
* @see Grammar#getNotReachableNonterminalSymbols()
*/
public final ArrayList < NonterminalSymbol > getNotReachableNonterminalSymbols ()
{
ArrayList < NonterminalSymbol > reachable = getReachableNonterminalSymbols ();
ArrayList < NonterminalSymbol > notReachable = new ArrayList < NonterminalSymbol > ();
for ( NonterminalSymbol current : this.nonterminalSymbolSet )
{
notReachable.add ( current );
}
for ( NonterminalSymbol current : reachable )
{
notReachable.remove ( current );
}
return notReachable;
}
/**
* Returns the {@link NonterminalSymbol}s which are not removeable from the
* {@link NonterminalSymbolSet}.
*
* @return The {@link NonterminalSymbol}s which are not removeable from the
* {@link NonterminalSymbolSet}.
*/
public final TreeSet < NonterminalSymbol > getNotRemoveableNonterminalSymbolsFromNonterminalSymbol ()
{
TreeSet < NonterminalSymbol > notRemoveableNonterminalSymbols = new TreeSet < NonterminalSymbol > ();
for ( Production current : this.productions )
{
notRemoveableNonterminalSymbols.add ( current.getNonterminalSymbol () );
for ( ProductionWordMember currentMember : current.getProductionWord () )
{
if ( currentMember instanceof NonterminalSymbol )
{
notRemoveableNonterminalSymbols
.add ( ( NonterminalSymbol ) currentMember );
}
}
}
return notRemoveableNonterminalSymbols;
}
/**
* Returns the {@link TerminalSymbol}s which are not removeable from the
* {@link TerminalSymbolSet}.
*
* @return The {@link TerminalSymbol}s which are not removeable from the
* {@link TerminalSymbolSet}.
*/
public final TreeSet < TerminalSymbol > getNotRemoveableTerminalSymbolsFromTerminalSymbol ()
{
TreeSet < TerminalSymbol > notRemoveableTerminalSymbols = new TreeSet < TerminalSymbol > ();
for ( Production current : this.productions )
{
for ( ProductionWordMember currentMember : current.getProductionWord () )
{
if ( currentMember instanceof TerminalSymbol )
{
notRemoveableTerminalSymbols.add ( ( TerminalSymbol ) currentMember );
}
}
}
return notRemoveableTerminalSymbols;
}
/**
* {@inheritDoc}
*
* @see Grammar#getProduction()
*/
public ArrayList < Production > getProduction ()
{
return this.productions;
}
/**
* {@inheritDoc}
*
* @see Grammar#getProductionForNonTerminal(NonterminalSymbol)
*/
public ArrayList < Production > getProductionForNonTerminal (
NonterminalSymbol s )
{
ArrayList < Production > prod = new ArrayList < Production > ();
for ( Production p : this.productions )
{
if ( p.getNonterminalSymbol ().equals ( s ) )
{
prod.add ( p );
}
}
return prod;
}
/**
* {@inheritDoc}
*
* @see Grammar#getProductionAt(int)
*/
public Production getProductionAt ( int index )
{
return this.productions.get ( index );
}
/**
* {@inheritDoc}
*
* @see Grammar#getReachableNonterminalSymbols()
*/
public final ArrayList < NonterminalSymbol > getReachableNonterminalSymbols ()
{
ArrayList < NonterminalSymbol > reachable = new ArrayList < NonterminalSymbol > ();
ArrayList < NonterminalSymbol > todoList = new ArrayList < NonterminalSymbol > ();
for ( NonterminalSymbol current : this.nonterminalSymbolSet )
{
if ( current.isStart () )
{
todoList.add ( current );
}
}
while ( todoList.size () > 0 )
{
NonterminalSymbol currentNonterminalSymbol = todoList.remove ( 0 );
reachable.add ( currentNonterminalSymbol );
ArrayList < Production > productionList = new ArrayList < Production > ();
for ( Production currentProduction : this.productions )
{
if ( currentProduction.getNonterminalSymbol ().equals (
currentNonterminalSymbol ) )
{
productionList.add ( currentProduction );
for ( ProductionWordMember currentMember : currentProduction
.getProductionWord () )
{
if ( currentMember instanceof DefaultNonterminalSymbol )
{
DefaultNonterminalSymbol currentNonterminalMember = ( DefaultNonterminalSymbol ) currentMember;
if ( !todoList.contains ( currentNonterminalMember )
&& !reachable.contains ( currentNonterminalMember ) )
{
todoList.add ( currentNonterminalMember );
}
}
}
}
}
}
return reachable;
}
/**
* {@inheritDoc}
*
* @see TableModel#getRowCount()
*/
public int getRowCount ()
{
return this.productions.size ();
}
/**
* {@inheritDoc}
*
* @see Grammar#getStartSymbol()
*/
public NonterminalSymbol getStartSymbol ()
{
return this.startSymbol;
}
/**
* Returns the {@link TerminalSymbolSet}.
*
* @return the {@link TerminalSymbolSet}.
*/
public TerminalSymbolSet getTerminalSymbolSet ()
{
return this.terminalSymbolSet;
}
/**
* {@inheritDoc}
*
* @see javax.swing.table.TableModel#getValueAt(int, int)
*/
public Object getValueAt ( int rowIndex, @SuppressWarnings ( "unused" )
int columnIndex )
{
return this.productions.get ( rowIndex );
}
/**
* {@inheritDoc}
*
* @see javax.swing.table.TableModel#isCellEditable(int, int)
*/
public boolean isCellEditable ( @SuppressWarnings ( "unused" )
int rowIndex, @SuppressWarnings ( "unused" )
int columnIndex )
{
return false;
}
/**
* {@inheritDoc}
*
* @see Modifyable#isModified()
*/
public final boolean isModified ()
{
if ( this.productions.size () != this.initialProductions.size () )
{
return true;
}
for ( int i = 0 ; i < this.productions.size () ; i++ )
{
if ( !this.productions.get ( i ).equals (
this.initialProductions.get ( i ) ) )
{
return true;
}
}
if ( this.nonterminalSymbolSet.isModified () )
{
return true;
}
if ( this.terminalSymbolSet.isModified () )
{
return true;
}
if ( !this.initialStartNonterminalSymbol.equals ( this.startSymbol ) )
{
return true;
}
for ( Production current : this.productions )
{
if ( current.isModified () )
{
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*
* @see Machine#removeModifyStatusChangedListener(ModifyStatusChangedListener)
*/
public final void removeModifyStatusChangedListener (
ModifyStatusChangedListener listener )
{
this.listenerList.remove ( ModifyStatusChangedListener.class, listener );
}
/**
* Remove a {@link Production} from this grammar.
*
* @param index The index of the {@link Production}.
*/
public void removeProduction ( int index )
{
this.productions.remove ( index );
updateStartSymbol ();
fireModifyStatusChanged ( false );
}
/**
* {@inheritDoc}
*
* @see javax.swing.table.TableModel#removeTableModelListener(javax.swing.event.TableModelListener)
*/
public void removeTableModelListener ( TableModelListener listener )
{
this.listenerList.remove ( TableModelListener.class, listener );
}
/**
* {@inheritDoc}
*
* @see Modifyable#resetModify()
*/
public final void resetModify ()
{
this.initialProductions.clear ();
this.initialProductions.addAll ( this.productions );
this.nonterminalSymbolSet.resetModify ();
this.terminalSymbolSet.resetModify ();
this.initialStartNonterminalSymbol = this.startSymbol;
for ( Production current : this.productions )
{
current.resetModify ();
}
}
/**
* Sets the {@link Production}s.
*
* @param productions The {@link Production}s to set.
* @see #productions
* @see Production
*/
public void setProductions ( ArrayList < Production > productions )
{
this.productions = productions;
}
/**
* {@inheritDoc}
*
* @see Grammar#setStartSymbol(NonterminalSymbol)
*/
public void setStartSymbol ( NonterminalSymbol startSymbol )
{
this.startSymbol = startSymbol;
updateStartSymbol ();
fireModifyStatusChanged ( false );
}
/**
* {@inheritDoc}
*
* @see TableModel#setValueAt(Object, int, int)
*/
public final void setValueAt ( @SuppressWarnings ( "unused" )
Object value, @SuppressWarnings ( "unused" )
int rowIndex, @SuppressWarnings ( "unused" )
int columnIndex )
{
// Do nothing
}
/**
* Updates the start symbol flags.
*/
public final void updateStartSymbol ()
{
for ( Production currentProduction : this.productions )
{
currentProduction.getNonterminalSymbol ()
.setStart (
currentProduction.getNonterminalSymbol ().equals (
this.startSymbol ) );
for ( ProductionWordMember currentMember : currentProduction
.getProductionWord () )
{
if ( currentMember instanceof NonterminalSymbol )
{
NonterminalSymbol currentSymbol = ( NonterminalSymbol ) currentMember;
currentSymbol.setStart ( currentSymbol.equals ( this.startSymbol ) );
}
}
}
for ( NonterminalSymbol current : this.nonterminalSymbolSet )
{
current.setStart ( current.equals ( this.startSymbol ) );
}
}
/**
* Validates that everything in the {@link AbstractMachine} is correct.
*
* @throws GrammarValidationException If the validation fails.
*/
public final void validate () throws GrammarValidationException
{
ArrayList < GrammarException > grammarExceptionList = new ArrayList < GrammarException > ();
if ( this.validationElementList
.contains ( ValidationElement.DUPLICATE_PRODUCTION ) )
{
grammarExceptionList.addAll ( checkDuplicateProduction () );
}
if ( this.validationElementList
.contains ( ValidationElement.NONTERMINAL_NOT_REACHABLE ) )
{
grammarExceptionList.addAll ( checkNonterminalNotReachable () );
}
if ( this.validationElementList
.contains ( ValidationElement.GRAMMAR_NOT_REGULAR ) )
{
grammarExceptionList.addAll ( checkRegularGrammar () );
}
// Throw the exception if a warning or an error has occurred.
if ( grammarExceptionList.size () > 0 )
{
throw new GrammarValidationException ( grammarExceptionList );
}
}
}