/*
* Copyright (c) 2009, Paul Merlin. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*
*/
package org.swing.on.steroids.wizard.presenters;
import java.util.*;
import org.codeartisans.java.toolbox.Collections;
import org.codeartisans.java.toolbox.exceptions.NullArgumentException;
import org.jgrapht.alg.CycleDetector;
import org.jgrapht.alg.DirectedNeighborIndex;
import org.jgrapht.graph.ClassBasedEdgeFactory;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.ListenableDirectedGraph;
import org.swing.on.steroids.wizard.model.WizardModel;
import org.swing.on.steroids.wizard.model.WizardPageID;
/**
* Default WizardGraph implementation based on {@link DefaultDirectedGraph}.
*
* All modifications must be done through the {@link ListenableDirectedGraph} decorator in order to ensure that the
* {@link DirectedNeighborIndex} is up to date.
*
* @author Paul Merlin
*/
@SuppressWarnings( "PackageVisibleField" )
public class DefaultWizardGraph<M extends WizardModel>
extends DefaultDirectedGraph<PageVertex<M>, TransitionEdge>
implements WizardGraph<M>
{
private static final long serialVersionUID = 1L;
private final ListenableDirectedGraph<PageVertex<M>, TransitionEdge> listenableDecorator;
private final DirectedNeighborIndex<PageVertex<M>, TransitionEdge> neighborsIndex;
/*
* package
*/ PageVertex<M> startPageVertex;
/*
* package
*/ PageVertex<M> currentPageVertex;
public DefaultWizardGraph()
{
super( new ClassBasedEdgeFactory<PageVertex<M>, TransitionEdge>( TransitionEdge.class ) );
listenableDecorator = new ListenableDirectedGraph<PageVertex<M>, TransitionEdge>( this );
neighborsIndex = new DirectedNeighborIndex<PageVertex<M>, TransitionEdge>( this );
listenableDecorator.addGraphListener( neighborsIndex );
}
@Override
public PageVertex<M> startPageVertex()
{
return startPageVertex;
}
@Override
public PageVertex<M> previousPageVertex()
{
if ( currentPageVertex == null ) {
return null;
}
Set<PageVertex<M>> predecessors = neighborsIndex.predecessorsOf( currentPageVertex );
predecessors = filterEnabledPredecessors( currentPageVertex, predecessors, true );
if ( predecessors.isEmpty() ) {
return null;
}
return Collections.firstElementOrNull( predecessors );
}
@Override
public PageVertex<M> currentPageVertex()
{
return currentPageVertex;
}
@Override
public void setCurrentPageVertex( PageVertex<M> pageVertex )
{
if ( !containsVertex( pageVertex ) ) {
throw new IllegalArgumentException( "WizardGraph does not contains this PageVertex: " + pageVertex );
}
if ( !currentWizardStepsPath().contains( pageVertex ) ) {
throw new IllegalArgumentException( "PageVertex is not in the current WizardGraph steps path: " + pageVertex );
}
currentPageVertex = pageVertex;
}
@Override
public PageVertex<M> nextPageVertex()
{
if ( currentPageVertex == null ) {
return null;
}
Set<PageVertex<M>> successors = neighborsIndex.successorsOf( currentPageVertex );
successors = filterEnabledSuccessors( currentPageVertex, successors, true );
if ( successors.isEmpty() ) {
return null;
}
return Collections.firstElementOrNull( successors );
}
@Override
public PageVertex<M> getPageVertex( WizardPageID pageID )
{
NullArgumentException.ensureNotNull( "PageID", pageID );
for ( PageVertex<M> eachPageVertex : vertexSet() ) {
if ( eachPageVertex.wizardPageID().equals( pageID ) ) {
return eachPageVertex;
}
}
return null;
}
@Override
public void addTransitionEdge( PageVertex<M> previous, PageVertex<M> next, Boolean enabled )
{
if ( !containsVertex( previous ) ) {
listenableDecorator.addVertex( previous );
}
if ( !containsVertex( next ) ) {
listenableDecorator.addVertex( next );
}
TransitionEdge transitionEdge = new TransitionEdge( previous, next, enabled );
if ( containsEdge( transitionEdge ) ) {
setTransitionEdgeEnabled( previous, next, enabled );
} else {
listenableDecorator.addEdge( previous, next, transitionEdge );
updateStartPageVertex();
}
}
@Override
public void setTransitionEdgeEnabled( PageVertex<M> previous, PageVertex<M> next, Boolean enabled )
{
TransitionEdge transitionEdge = getEdge( previous, next );
NullArgumentException.ensureNotNull( "Transition from " + previous + " to " + next, transitionEdge );
transitionEdge.setEnabled( enabled );
updateStartPageVertex();
}
@Override
public void applyTransitionChanges( Iterable<TransitionChange> changes )
{
if ( changes != null ) {
Map<TransitionEdge, Boolean> newStatuses = new HashMap<TransitionEdge, Boolean>();
for ( TransitionChange eachChange : changes ) {
PageVertex<M> previous = getPageVertex( eachChange.getPreviousID() );
PageVertex<M> next = getPageVertex( eachChange.getNextID() );
TransitionEdge eachEdge = getEdge( previous, next );
NullArgumentException.ensureNotNull( "Transition from " + previous + " to " + next, eachEdge );
newStatuses.put( eachEdge, eachChange.isEnabled() );
}
for ( Map.Entry<TransitionEdge, Boolean> eachEntry : newStatuses.entrySet() ) {
eachEntry.getKey().setEnabled( eachEntry.getValue() );
}
updateStartPageVertex();
}
}
private void updateStartPageVertex()
{
Set<PageVertex<M>> vertexSet = vertexSet();
if ( !vertexSet.isEmpty() ) {
if ( vertexSet.size() == 2 ) {
startPageVertex = Collections.firstElementOrNull( vertexSet );
currentPageVertex = startPageVertex;
} else {
List<PageVertex<M>> currentWizardStepsPath = currentWizardStepsPath();
if ( !currentWizardStepsPath.isEmpty() ) {
startPageVertex = currentWizardStepsPath().get( 0 );
}
}
}
}
@Override
public List<PageVertex<M>> currentWizardStepsPath()
{
List<PageVertex<M>> stepsPath = new ArrayList<PageVertex<M>>();
if ( currentPageVertex != null ) {
List<PageVertex<M>> previouses = new ArrayList<PageVertex<M>>();
PageVertex<M> eachPageVertex = assertOneEnabledPredecessorOrNullIfNone( currentPageVertex );
while ( eachPageVertex != null ) {
previouses.add( eachPageVertex );
eachPageVertex = assertOneEnabledPredecessorOrNullIfNone( eachPageVertex );
}
List<PageVertex<M>> nexts = new ArrayList<PageVertex<M>>();
eachPageVertex = assertOneEnabledSuccessorOrNullIfNone( currentPageVertex );
while ( eachPageVertex != null ) {
nexts.add( eachPageVertex );
eachPageVertex = assertOneEnabledSuccessorOrNullIfNone( eachPageVertex );
}
java.util.Collections.reverse( previouses );
stepsPath.addAll( previouses );
stepsPath.add( currentPageVertex );
stepsPath.addAll( nexts );
}
return stepsPath;
}
@Override
public void assertStepsPathUnicity()
{
CycleDetector<PageVertex<M>, TransitionEdge> cd = new CycleDetector<PageVertex<M>, TransitionEdge>( this );
if ( cd.detectCycles() ) {
throw new WizardGraphHasCyclesException( cd.findCycles() );
}
// Prevent deactivated edges
// StrongConnectivityInspector<PageVertex, TransitionEdge> sci = new StrongConnectivityInspector<PageVertex, TransitionEdge>( this );
// if ( !sci.isStronglyConnected() ) {
// throw new WizardGraphIsNotStronglyConnected( sci.stronglyConnectedSets() );
// }
}
private PageVertex<M> assertOneEnabledPredecessorOrNullIfNone( PageVertex<M> pageVertex )
{
Set<PageVertex<M>> predecessors = neighborsIndex.predecessorsOf( pageVertex );
predecessors = filterEnabledPredecessors( pageVertex, predecessors, true );
if ( !predecessors.isEmpty() && predecessors.size() > 1 ) {
throw new RuntimeException();
}
return Collections.firstElementOrNull( predecessors );
}
private Set<PageVertex<M>> filterEnabledPredecessors( PageVertex<M> pivot, Set<PageVertex<M>> predecessors, boolean enabled )
{
Set<PageVertex<M>> filtered = new HashSet<PageVertex<M>>();
for ( PageVertex<M> eachPredecessor : predecessors ) {
TransitionEdge edge = this.getEdge( eachPredecessor, pivot );
if ( edge.isEnabled() == enabled ) {
filtered.add( eachPredecessor );
}
}
return filtered;
}
private PageVertex<M> assertOneEnabledSuccessorOrNullIfNone( PageVertex<M> pageVertex )
{
Set<PageVertex<M>> successors = neighborsIndex.successorsOf( pageVertex );
successors = filterEnabledSuccessors( pageVertex, successors, true );
if ( !successors.isEmpty() && successors.size() > 1 ) {
throw new RuntimeException();
}
return Collections.firstElementOrNull( successors );
}
private Set<PageVertex<M>> filterEnabledSuccessors( PageVertex<M> pivot, Set<PageVertex<M>> successors, boolean enabled )
{
Set<PageVertex<M>> filtered = new HashSet<PageVertex<M>>();
for ( PageVertex<M> eachSuccessor : successors ) {
TransitionEdge edge = this.getEdge( pivot, eachSuccessor );
if ( edge.isEnabled() == enabled ) {
filtered.add( eachSuccessor );
}
}
return filtered;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder( "DefaultWizardGraph{\n" );
sb.append( "\tVERTEXES\n" );
for ( PageVertex<M> eachVertex : vertexSet() ) {
sb.append( "\t\t" ).append( eachVertex ).append( "\n" );
}
sb.append( "\tEDGES\n" );
for ( TransitionEdge eachEdge : edgeSet() ) {
sb.append( "\t\t" ).append( eachEdge ).append( "\n" );
}
sb.append( "\tSTART VERTEX " ).append( startPageVertex ).append( "\n" );
sb.append( "\tCURRENT VERTEX " ).append( currentPageVertex ).append( "\n" );
sb.append( "\tSTEPS PATHS " ).append( Arrays.toString( currentWizardStepsPath().toArray() ) ).append( "\n" );
return sb.append( "}" ).toString();
}
}