All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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();
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy