org.codeartisans.java.sos.wizard.presenters.DefaultWizardGraph Maven / Gradle / Ivy
The newest version!
/*
* 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.codeartisans.java.sos.wizard.presenters;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.codeartisans.java.sos.wizard.model.WizardPageID;
import org.codeartisans.java.sos.wizard.model.WizardModel;
import org.codeartisans.java.toolbox.CollectionUtils;
import org.codeartisans.java.toolbox.exceptions.NullArgumentException;
import org.jgrapht.alg.CycleDetector;
import org.jgrapht.alg.DirectedNeighborIndex;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.ClassBasedEdgeFactory;
import org.jgrapht.graph.ListenableDirectedGraph;
/**
* 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
extends DefaultDirectedGraph, TransitionEdge>
implements WizardGraph
{
private static final long serialVersionUID = 1L;
private final ListenableDirectedGraph, TransitionEdge> listenableDecorator;
private final DirectedNeighborIndex, TransitionEdge> neighborsIndex;
/* package */ PageVertex startPageVertex;
/* package */ PageVertex currentPageVertex;
public DefaultWizardGraph()
{
super( new ClassBasedEdgeFactory, TransitionEdge>( TransitionEdge.class ) );
listenableDecorator = new ListenableDirectedGraph, TransitionEdge>( this );
neighborsIndex = new DirectedNeighborIndex, TransitionEdge>( this );
listenableDecorator.addGraphListener( neighborsIndex );
}
@Override
public PageVertex startPageVertex()
{
return startPageVertex;
}
@Override
public PageVertex previousPageVertex()
{
if ( currentPageVertex == null ) {
return null;
}
Set> predecessors = neighborsIndex.predecessorsOf( currentPageVertex );
predecessors = filterEnabledPredecessors( currentPageVertex, predecessors, true );
if ( predecessors.isEmpty() ) {
return null;
}
return CollectionUtils.firstElementOrNull( predecessors );
}
@Override
public PageVertex currentPageVertex()
{
return currentPageVertex;
}
@Override
public void setCurrentPageVertex( PageVertex 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 nextPageVertex()
{
if ( currentPageVertex == null ) {
return null;
}
Set> successors = neighborsIndex.successorsOf( currentPageVertex );
successors = filterEnabledSuccessors( currentPageVertex, successors, true );
if ( successors.isEmpty() ) {
return null;
}
return CollectionUtils.firstElementOrNull( successors );
}
@Override
public PageVertex getPageVertex( WizardPageID pageID )
{
NullArgumentException.ensureNotNull( "PageID", pageID );
for ( PageVertex eachPageVertex : vertexSet() ) {
if ( eachPageVertex.wizardPageID().equals( pageID ) ) {
return eachPageVertex;
}
}
return null;
}
@Override
public void addTransitionEdge( PageVertex previous, PageVertex 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 previous, PageVertex 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 changes )
{
if ( changes != null ) {
Map newStatuses = new HashMap();
for ( TransitionChange eachChange : changes ) {
PageVertex previous = getPageVertex( eachChange.getPreviousID() );
PageVertex 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 eachEntry : newStatuses.entrySet() ) {
eachEntry.getKey().setEnabled( eachEntry.getValue() );
}
updateStartPageVertex();
}
}
private void updateStartPageVertex()
{
Set> vertexSet = vertexSet();
if ( !vertexSet.isEmpty() ) {
if ( vertexSet.size() == 2 ) {
startPageVertex = CollectionUtils.firstElementOrNull( vertexSet );
currentPageVertex = startPageVertex;
} else {
List> currentWizardStepsPath = currentWizardStepsPath();
if ( !currentWizardStepsPath.isEmpty() ) {
startPageVertex = currentWizardStepsPath().get( 0 );
}
}
}
}
@Override
public List> currentWizardStepsPath()
{
List> stepsPath = new ArrayList>();
if ( currentPageVertex != null ) {
List> previouses = new ArrayList>();
PageVertex eachPageVertex = assertOneEnabledPredecessorOrNullIfNone( currentPageVertex );
while ( eachPageVertex != null ) {
previouses.add( eachPageVertex );
eachPageVertex = assertOneEnabledPredecessorOrNullIfNone( eachPageVertex );
}
List> nexts = new ArrayList>();
eachPageVertex = assertOneEnabledSuccessorOrNullIfNone( currentPageVertex );
while ( eachPageVertex != null ) {
nexts.add( eachPageVertex );
eachPageVertex = assertOneEnabledSuccessorOrNullIfNone( eachPageVertex );
}
Collections.reverse( previouses );
stepsPath.addAll( previouses );
stepsPath.add( currentPageVertex );
stepsPath.addAll( nexts );
}
return stepsPath;
}
@Override
public void assertStepsPathUnicity()
{
CycleDetector, TransitionEdge> cd = new CycleDetector, TransitionEdge>( this );
if ( cd.detectCycles() ) {
throw new WizardGraphHasCyclesException( cd.findCycles() );
}
// Prevent deactivated edges
// StrongConnectivityInspector sci = new StrongConnectivityInspector( this );
// if ( !sci.isStronglyConnected() ) {
// throw new WizardGraphIsNotStronglyConnected( sci.stronglyConnectedSets() );
// }
}
private PageVertex assertOneEnabledPredecessorOrNullIfNone( PageVertex pageVertex )
{
Set> predecessors = neighborsIndex.predecessorsOf( pageVertex );
predecessors = filterEnabledPredecessors( pageVertex, predecessors, true );
if ( !predecessors.isEmpty() && predecessors.size() > 1 ) {
throw new RuntimeException();
}
return CollectionUtils.firstElementOrNull( predecessors );
}
private Set> filterEnabledPredecessors( PageVertex pivot, Set> predecessors, boolean enabled )
{
Set> filtered = new HashSet>();
for ( PageVertex eachPredecessor : predecessors ) {
TransitionEdge edge = this.getEdge( eachPredecessor, pivot );
if ( edge.isEnabled() == enabled ) {
filtered.add( eachPredecessor );
}
}
return filtered;
}
private PageVertex assertOneEnabledSuccessorOrNullIfNone( PageVertex pageVertex )
{
Set> successors = neighborsIndex.successorsOf( pageVertex );
successors = filterEnabledSuccessors( pageVertex, successors, true );
if ( !successors.isEmpty() && successors.size() > 1 ) {
throw new RuntimeException();
}
return CollectionUtils.firstElementOrNull( successors );
}
private Set> filterEnabledSuccessors( PageVertex pivot, Set> successors, boolean enabled )
{
Set> filtered = new HashSet>();
for ( PageVertex 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 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();
}
}