pluf/workflow

View on GitHub
src/Imp/StateMachineBuilderImpl.php

Summary

Maintainability
F
4 days
Test Coverage
<?php
namespace Pluf\Workflow\Imp;

use Pluf\Di\Container;
use Pluf\Workflow\Action;
use Pluf\Workflow\ActionExecutionService;
use Pluf\Workflow\Conditions;
use Pluf\Workflow\HistoryType;
use Pluf\Workflow\MutableState;
use Pluf\Workflow\MutableTransition;
use Pluf\Workflow\StateMachine;
use Pluf\Workflow\StateMachineBuilder;
use Pluf\Workflow\StateMachineConfiguration;
use Pluf\Workflow\TransitionType;
use Pluf\Workflow\UntypedStateMachineBuilder;
use Pluf\Workflow\Actions\FinalStateGuardAction;
use Pluf\Workflow\Attributes\State;
use Pluf\Workflow\Attributes\Transit;
use Pluf\Workflow\Builder\DeferBoundActionBuilder;
use Pluf\Workflow\Builder\EntryExitActionBuilder;
use Pluf\Workflow\Builder\ExternalTransitionBuilder;
use Pluf\Workflow\Builder\InternalTransitionBuilder;
use Pluf\Workflow\Builder\LocalTransitionBuilder;
use Pluf\Workflow\Builder\MultiTransitionBuilder;
use Pluf\Workflow\Component\IdProviderUUID;
use Pluf\Workflow\Exceptions\IllegalArgumentException;
use Pluf\Workflow\Exceptions\IllegalStateException;
use ArrayObject;
use ReflectionClass;
use Throwable;
use Pluf\Workflow\Conditions\Always;
use Pluf\Workflow\Conditions\Never;

class StateMachineBuilderImpl implements UntypedStateMachineBuilder, StateMachineBuilder
{
    use AssertTrait;

    // static {
    // DuplicateChecker.checkDuplicate(StateMachineBuilder.class);
    // }

    // private static final Logger logger = LoggerFactory.getLogger(StateMachineBuilderImpl.class);
    private $container = null;

    public ArrayObject $states;

    private ?string $stateMachineImplClazz;

    private ?string $stateType;

    private ?string $eventType;

    private ?string $contextType;

    private bool $prepared = false;

    // private final Constructor<? extends T> constructor;
    private ?string $postConstructMethod = null;

    protected $stateConverter;

    protected $eventConverter;

    private array $methodCallParamTypes = [];

    private array $stateAliasToDescription = [];

    private $scriptManager;

    // MvelScriptManager
    private $startEvent;

    private $finishEvent;

    private $terminateEvent;

    // Not supported, a DI system is replaced
    private ?ActionExecutionService $actionExecutionService = null;

    // ExecutionContext
    private array $deferBoundActionInfoList = [];

    private bool $scanAnnotations = true;

    private array $extraParamTypes = [];

    private ?StateMachineConfiguration $stateMachineConfiguration = null;

    public function __construct()
    {
        $this->states = new ArrayObject();
    }

    private function checkState()
    {
        if ($this->prepared) {
            throw new IllegalStateException("The state machine builder has been freezed and cannot be changed anymore.");
        }
    }

    /**
     * Prepares the builder
     *
     * It may load all descriptions from the state machine class and merge with
     * flute api.
     */
    private function prepare(): void
    {
        if ($this->prepared) {
            return;
        }

        $container = $this->getContainer();
        $container['stateMachinBuilder'] = Container::value($this);

        if ($this->scanAnnotations) {
            // 1. install all the declare states, states must be installed before installing transition and extension methods
            $this->walkThroughStateMachineClassForState();
            // 2. install all the declare transitions
            $this->walkThroughStateMachineClassForTransition();
            // 2.5 install all the defer bound actions
            $this->installDeferBoundActions();
        }
        // 3. install all the extension method call when state machine builder freeze
        $this->installExtensionMethods();
        // 4. prioritize transitions
        $this->prioritizeTransitions();
        // 5. install final state actions
        $this->installFinalStateActions();
        // 6. verify correctness of state machine
        $this->verifyStateMachineDefinition();
        // 7. proxy untyped states
        $this->proxyUntypedStates();
        $this->prepared = true;
    }

    private function walkThroughStateMachineClassForState(): void
    {
        $stack = [];
        array_push($stack, new ReflectionClass($this->stateMachineImplClazz));
        while (! empty($stack)) {
            // Adds states
            $k = array_pop($stack);
            $states = $k->getAttributes(State::class);
            foreach ($states as $state) {
                $this->buildDeclareState($state->newInstance());
            }

            // push supper classess for next itteration
            forEach ($k->getInterfaces() as $i) {
                if ($this->isStateMachineInterface($i)) {
                    array_push($stack, $i);
                }
            }
            if ($this->isStateMachineType($k->getParentClass())) {
                array_push($stack, $k->getParentClass());
            }
        }
    }

    private function walkThroughStateMachineClassForTransition(): void
    {
        $stack = [];
        array_push($stack, new ReflectionClass($this->stateMachineImplClazz));
        while (! empty($stack)) {
            // Adds states
            $k = array_pop($stack);
            $transitions = $k->getAttributes(Transit::class);
            foreach ($transitions as $transit) {
                $this->buildDeclareTransition($transit->newInstance());
            }

            // push supper classess for next itteration
            forEach ($k->getInterfaces() as $i) {
                if ($this->isStateMachineInterface($i)) {
                    array_push($stack, $i);
                }
            }
            if ($this->isStateMachineType($k->getParentClass())) {
                array_push($stack, $k->getParentClass());
            }
        }
    }

    private function buildDeclareState(State $state): void
    {
        // Preconditions.checkState(stateConverter!=null, "Do not register state converter");
        $stateId = $state->name;
        // Preconditions.checkNotNull(stateId, "Cannot convert state of name \""+state.name()+"\".");
        $newState = $this->defineState($stateId);
        $newState->setCompositeType($state->compositeType);
        if (! $newState->isParallelState()) {
            $newState->setHistoryType($state->historyType);
        }
        $newState->setFinal($state->finalState);

        if (! empty($state->parent)) {
            $parentStateId = $this->parseStateId($state->parent);
            $parentState = $this->defineState($parentStateId);
            $newState->setParentState($parentState);
            $parentState->addChildState($newState);
            if (! $parentState->isParallelState() && $state->initialState) {
                $parentState->setInitialState($newState);
            }
        }

        if (! empty($state->entryCallMethod)) {
            $methodCallAction = FSM::newMethodCallAction($state->entryCallMethod);
            $this->onEntry($stateId)->perform($methodCallAction);
        }

        if (! empty($state->exitCallMethod)) {
            $methodCallAction = FSM::newMethodCallActionProxy($state->exitCallMetho, $this->executionContext);
            $this->onExit($stateId)->perform($methodCallAction);
        }
        $this->rememberStateAlias($state);
    }

    private function buildDeclareTransition(Transit $transit): void
    {
        if (empty($transit)) {
            return;
        }

        // TODO: support sate converters
        // Preconditions.checkState(stateConverter!=null, "Do not register state converter");
        // Preconditions.checkState(eventConverter!=null, "Do not register event converter");

        // if not explicit specify 'from', 'to' and 'event', it is declaring a defer bound action.
        if ($this->isDeferBoundAction($transit)) {
            $this->buildDeferBoundAction($transit);
            return;
        }
        
        $when = $transit->getWhen();
        $this->assertTrue($this->isInstantiableType($when), "Condition \'when\' should be concrete class or static inner class.");
        $this->assertTrue($transit->type != TransitionType::INTERNAL || $transit->from == $transit->to, "Internal transition must transit to the same source state.");

        // $fromState = stateConverter.convertFromString(parseStateId(transit.from()));
        $fromState = $this->parseStateId($transit->from);
        $this->assertNotEmpty($fromState, "Source state not found.");
        // $toState = stateConverter.convertFromString(parseStateId(transit.to()));
        $toState = $this->parseStateId($transit->to);
        // $event = eventConverter.convertFromString(transit.on());
        $event = $transit->on;
        $this->assertNotEmpty($event, "Event not found.");

        // check exited transition which satisfied the criteria
        if ($this->states->offsetExists($fromState)) {
            $theFromState = $this->states[$fromState];
            foreach ($theFromState->getAllTransitions() as $t) {
                if ($t->isMatch($fromState, $toState, $event, $transit->priority, $when, $transit->type)) {
                    $mutableTransition = $t;
                    $callMethodExpression = $transit->callMethod;
                    if (! empty($callMethodExpression)) {
                        $methodCallAction = FSM::newMethodCallAction($callMethodExpression);
                        $mutableTransition->addAction($methodCallAction);
                    }
                    return;
                }
            }
        }

        // if no existed transition is matched then create a new transition
        $toBuilder = null;
        if ($transit->type == TransitionType::INTERNAL) {
            $transitionBuilder = FSM::newInternalTransitionBuilder($this->states, $transit->priority);
            $toBuilder = $transitionBuilder->within($fromState);
        } else {
            $transitionBuilder = ($transit->type == TransitionType::LOCAL) ? FSM::newLocalTransitionBuilder($this->states, $transit->priority) : FSM::newExternalTransitionBuilder($this->states, $transit->priority);
            $fromBuilder = $transitionBuilder->from($fromState);
            $isTargetFinal = $transit->targetFinal || FSM::getState($this->states, $toState)->isFinalState();
            $toBuilder = $isTargetFinal ? $fromBuilder->toFinal($toState) : $fromBuilder->to($toState);
        }
        $onBuilder = $toBuilder->on($event);
        $c = null;
        try {
            if ($transit->when != 'Always') {
                $constructor = $when;
                // TODO: maso, 2021: use invoker to instance
                $c = new $constructor();
            } else if (! empty($transit->whenMvel)) {
                $c = FSM::newMvelCondition($transit->whenMvel);
            }
        } catch (Throwable $e) {
            // logger.error("Instantiate Condition \""+transit.when().getName()+"\" failed.");
            $c = Conditions::never();
        }
        $whenBuilder = $c != null ? $onBuilder->when($c) : $onBuilder;

        if (! empty($transit->callMethod)) {
            $methodCallAction = FSM::newMethodCallAction($transit->callMethod);
            $whenBuilder->perform($methodCallAction);
        }
    }

    /**
     * Checks if many source, distance or event must support
     *
     * @param Transit $transit
     * @return bool
     */
    private function isDeferBoundAction(Transit $transit): bool
    {
        return "*" == $transit->from || "*" == $transit->to || "*" == $transit->on;
    }

    /**
     * add alias
     *
     * @param State $state
     */
    private function rememberStateAlias(State $state): void
    {
        if (empty($state->alias)) {
            return;
        }
        $this->assertFalse(array_key_exists($state->alias, $this->stateAliasToDescription), "Cannot define duplicate state alias \"{state.alias}\" for state \"{state.name}\" and \"{other}\".", [
            'state' => $state,
            'other' => $this->stateAliasToDescription[$state->alias]
        ]);
        $this->stateAliasToDescription[$state->alias] = $state->name;
    }

    /**
     * Convert alias or name into state name
     *
     * State alias starts with #
     *
     * @param string $value
     * @return string
     */
    private function parseStateId(string $value): string
    {
        return (isset($value) && str_starts_with($value, "#")) ? $this->stateAliasToDescription[substr($value, 1)] : $value;
    }

    private function installDeferBoundActions(): void
    {
        // if(empty($this->deferBoundActionInfoList)){
        // return;
        // }
        foreach ($this->deferBoundActionInfoList as $deferBoundActionInfo) {
            $this->installDeferBoundAction($deferBoundActionInfo);
        }
    }

    private function installDeferBoundAction(DeferBoundActionInfo $deferBoundActionInfo)
    {
        foreach ($this->states as $mutableState) {
            if (! $deferBoundActionInfo->isFromStateMatch($mutableState->getStateId())) {
                continue;
            }
            $trs = $mutableState->getAllTransitions();
            foreach ($trs as $transition) {
                if ($deferBoundActionInfo->isToStateMatch($transition->getTargetState()
                    ->getStateId()) && $deferBoundActionInfo->isEventStateMatch($transition->getEvent())) {
                    $transition->addActions($deferBoundActionInfo->getActions());
                }
            }
        }
    }

    private function installExtensionMethods(): void
    {
        foreach ($this->states as $state) {
            // Ignore all the transition start from a final state
            if ($state->isFinalState()) {
                continue;
            }

            // state exit extension method
            $exitMethodCallCandidates = $this->getEntryExitStateMethodNames($state, false);
            foreach ($exitMethodCallCandidates as $exitMethodCallCandidate) {
                $this->addStateEntryExitMethodCallAction($exitMethodCallCandidate, $this->methodCallParamTypes, $state, false);
            }

            // transition extension methods
            $trx = $state->getAllTransitions();
            foreach ($trx as $transition) {
                $transitionMethodCallCandidates = $this->getTransitionMethodNames($transition);
                foreach ($transitionMethodCallCandidates as $transitionMethodCallCandidate) {
                    $this->addTransitionMethodCallAction($transitionMethodCallCandidate, $this->methodCallParamTypes, $transition);
                }
            }

            // state entry extension method
            $entryMethodCallCandidates = $this->getEntryExitStateMethodNames($state, true);
            foreach ($entryMethodCallCandidates as $entryMethodCallCandidate) {
                $this->addStateEntryExitMethodCallAction($entryMethodCallCandidate, $this->methodCallParamTypes, $state, true);
            }
        }
    }

    private function addTransitionMethodCallAction(string $methodName, $parameterTypes, MutableTransition $mutableTransition): void
    {
        if ($this->hasMethod($this->stateMachineImplClazz, $methodName)) {
            $methodCallAction = FSM::newMethodCallAction($methodName, Action::EXTENSION_WEIGHT);
            $mutableTransition->addAction($methodCallAction);
        }
    }

    private function addStateEntryExitMethodCallAction(string $methodName, $parameterTypes, MutableState $mutableState, bool $isEntryAction): void
    {
        if ($this->hasMethod($this->stateMachineImplClazz, $methodName)) {
            $weight = Action::EXTENSION_WEIGHT;
            if (str_starts_with($methodName, "before")) {
                $weight = Action::BEFORE_WEIGHT;
            } else if (str_starts_with($methodName, "after")) {
                $weight = Action::AFTER_WEIGHT;
            }
            $methodCallAction = FSM::newMethodCallAction($methodName, $weight);
            if ($isEntryAction) {
                $mutableState->addEntryAction($methodCallAction);
            } else {
                $mutableState->addExitAction($methodCallAction);
            }
        }
    }

    private function getEntryExitStateMethodNames($state, bool $isEntry): array
    {
        $prefix = ($isEntry ? "entry" : "exit");
        $postfix = ($isEntry ? "EntryAny" : "ExitAny");
        return [
            "before" . $postfix,
            $prefix . ucfirst(($this->stateConverter != null && ! $state->isFinalState()) ? $this->stateConverter->convertToString($state->getStateId()) : $state),
            "after" . $postfix
        ];
    }

    private function getTransitionMethodNames($transition): array
    {
        $fromState = $transition->getSourceState();
        $toState = $transition->getTargetState();
        $event = $transition->getEvent();
        $fromStateName = ucfirst($this->stateConverter != null ? $this->stateConverter . convertToString($fromState->getStateId()) : $fromState);
        $toStateName = ucfirst(($this->stateConverter != null && ! $toState->isFinalState()) ? $this->stateConverter->convertToString($toState->getStateId()) : $toState);
        $eventName = ucfirst($this->eventConverter != null ? $this->eventConverter->convertToString($event) : $event);
        $conditionName = ucfirst($transition->getCondition()->name());
        return [
            "transitFrom" . $fromStateName . "To" . $toStateName . "On" . $eventName . "When" . $conditionName,
            "transitFrom" . $fromStateName . "To" . $toStateName . "On" . $eventName,
            "transitFromAnyTo" . $toStateName . "On" . $eventName,
            "transitFrom" . $fromStateName . "ToAnyOn" . $eventName,
            "transitFrom" . $fromStateName . "To" . $toStateName,
            "on" . $eventName
        ];
    }

    private function prioritizeTransitions(): void
    {
        foreach ($this->states as $state) {
            if ($state->isFinalState()) {
                continue;
            }
            $state->prioritizeTransitions();
        }
    }

    private function installFinalStateActions(): void
    {
        foreach ($this->states as $state) {
            if (! $state->isFinalState()) {
                continue;
            }
            // defensive code: final state cannot be exited anymore
            $state->addExitAction(new FinalStateGuardAction());
        }
    }

    private function verifyStateMachineDefinition(): void
    {
        foreach ($this->states as $state) {
            $state->verify();
        }
    }

    private function proxyUntypedStates(): void
    {
        // NOTE: untyped FSM is not the case in PHP
        // if(UntypedStateMachine.class.isAssignableFrom(stateMachineImplClazz)) {
        // $untypedStates = [];
        // foreach($this->states as $state) {
        // UntypedMutableState untypedState = (UntypedMutableState) Proxy.newProxyInstance(
        // UntypedMutableState.class.getClassLoader(),
        // new Class[]{UntypedMutableState.class, UntypedImmutableState.class},
        // new InvocationHandler() {
        // @Override
        // public Object invoke(Object proxy, Method method, Object[] args)
        // throws Throwable {
        // if (method.getName().equals("getStateId")) {
        // return state.getStateId();
        // } else if(method.getName().equals("getThis")) {
        // return state.getThis();
        // } else if(method.getName().equals("equals")) {
        // return state.equals(args[0]);
        // } else if(method.getName().equals("hashCode")) {
        // return state.hashCode();
        // }
        // return method.invoke(state, args);
        // }
        // });
        // untypedStates.put(state.getStateId(), MutableState.class.cast(untypedState));
        // }
        // states.clear();
        // states.putAll(untypedStates);
        // }
    }

    /**
     *
     * @deprecated use searchMethod
     * @param string $target
     * @param string $methodName
     * @return NULL
     */
    protected function findMethodCallActionInternal(string $target, string $methodName)
    {
        return $this->hasMethod($target, $methodName);
    }

    protected function hasMethod(string $targetClass, string $name)
    {
        $reflectionClass = new ReflectionClass($targetClass);
        return $reflectionClass->hasMethod($name);
    }

    public function setStateMachinClass($stateMachineClass): self
    {
        $this->checkState();
        $this->stateMachineImplClazz = $stateMachineClass;
        return $this;
    }

    public function setContainer(Container $container): self
    {
        $this->checkState();
        $this->container = $container;
        return $this;
    }

    protected function getContainer(): Container
    {
        if (! isset($this->container)) {
            $this->container = new Container();
        }
        return $this->container;
    }

    public function setStateType($stateType): self
    {
        $this->checkState();
        $this->stateType = $stateType;
        return $this;
    }

    public function setEventType($eventType): self
    {
        $this->checkState();
        $this->eventType = $eventType;
        return $this;
    }

    public function setContextType($contextType): self
    {
        $this->checkState();
        $this->contextType = $contextType;
        return $this;
    }

    private function isInstantiableType(?string $type = null): bool
    {
        if (! isset($type)) {
            return false;
        }
        $reflection = new \ReflectionClass($type);
        return $reflection->isInstantiable();
    }

    private function isStateMachineType(string $stateMachineClazz): bool
    {
        return is_subclass_of($stateMachineClazz, StateMachineImpl::class, true);
        // stateMachineClazz!= null && AbstractStateMachine.class != stateMachineClazz &&
        // AbstractStateMachine.class.isAssignableFrom(stateMachineClazz);
    }

    private function isStateMachineInterface(string $stateMachineClazz): bool
    {
        return is_subclass_of($stateMachineClazz, StateMachine::class, true);
        // return stateMachineClazz!= null && stateMachineClazz.isInterface() &&
        // StateMachine.class.isAssignableFrom(stateMachineClazz);
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\UntypedStateMachineBuilder::newAnyStateMachine()
     */
    public function newAnyStateMachine($initialStateId, StateMachineConfiguration $configuration, ...$extraParams)
    {
        throw new \RuntimeException('Not implements');
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::externalTransition()
     */
    public function externalTransition(int $priority = 1): ExternalTransitionBuilder
    {
        $this->checkState();
        return FSM::newExternalTransitionBuilder($this->states, $priority);
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::defineTerminateEvent()
     */
    public function defineTerminateEvent($terminateEvent): void
    {
        throw new \RuntimeException('Not implements');
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::defineStartEvent()
     */
    public function defineStartEvent($startEvent): void
    {
        throw new \RuntimeException('Not implements');
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::onExit()
     */
    public function onExit($stateId): EntryExitActionBuilder
    {
        $this->checkState();
        $state = FSM::getState($this->states, $stateId);
        return FSM::newEntryExitActionBuilder($state, false);
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::externalTransitions()
     */
    public function externalTransitions(int $priority = 0): MultiTransitionBuilder
    {
        throw new \RuntimeException('Not implements');
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::defineLinkedState()
     */
    public function defineLinkedState($stateId, $linkedStateMachineBuilder, $initialLinkedState, $extraParams): MutableState
    {
        throw new \RuntimeException('Not implements');
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::defineState()
     */
    public function defineState($stateId): MutableState
    {
        $this->checkState();
        return FSM::getState($this->states, $stateId);
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::transitions()
     */
    public function transitions(int $priority = 0): MultiTransitionBuilder
    {
        throw new \RuntimeException('Not implements');
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::defineFinishEvent()
     */
    public function defineFinishEvent($finishEvent): void
    {
        throw new \RuntimeException('Not implements');
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::transition()
     */
    public function transition(int $priority = 0): ExternalTransitionBuilder
    {
        throw new \RuntimeException('Not implements');
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::defineSequentialStatesOn()
     */
    public function defineSequentialStatesOn($parentStateId, $childStateIds, ?HistoryType $historyType = null): void
    {
        throw new \RuntimeException('Not implements');
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::setStateMachineConfiguration()
     */
    public function setStateMachineConfiguration(?StateMachineConfiguration $configure = null): self
    {
        $this->stateMachineConfiguration = $configure;
    }

    /**
     * Gets the state machine configurateion
     *
     * A new instance will be created if no configuration has not setted
     *
     * @return StateMachineConfiguration the configuration
     */
    protected function getStateMachineConfiguration(): StateMachineConfiguration
    {
        if (! isset($this->stateMachineConfiguration)) {
            $this->stateMachineConfiguration = StateMachineConfiguration::create();
            $this->stateMachineConfiguration->setIdProvider(new IdProviderUUID());
        }
        return $this->stateMachineConfiguration;
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\UntypedStateMachineBuilder::newUntypedStateMachine()
     */
    public function newUntypedStateMachine($initialStateId, StateMachineConfiguration $configuration, ...$extraParams)
    {
        throw new \RuntimeException('Not implements');
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::transit()
     */
    public function transit(): DeferBoundActionBuilder
    {
        throw new \RuntimeException('Not implements');
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::defineTimedState()
     */
    public function defineTimedState($stateId, int $initialDelay, int $timeInterval, $autoEvent, $autoContext): MutableState
    {
        throw new \RuntimeException('Not implements');
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::localTransitions()
     */
    public function localTransitions(int $priority = 0): MultiTransitionBuilder
    {
        throw new \RuntimeException('Not implements');
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::internalTransition()
     */
    public function internalTransition(int $priority = 0): InternalTransitionBuilder
    {
        throw new \RuntimeException('Not implements');
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::defineFinalState()
     */
    public function defineFinalState($stateId): MutableState
    {
        $this->checkState();
        $newState = $this->defineState($stateId);
        $newState->setFinal(true);
        return $newState;
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::defineParallelStatesOn()
     */
    public function defineParallelStatesOn($parentStateId, $childStateIds): void
    {
        throw new \RuntimeException('Not implements');
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::onEntry()
     */
    public function onEntry($stateId): EntryExitActionBuilder
    {
        throw new \RuntimeException('Not implements');
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::defineNoInitSequentialStatesOn()
     */
    public function defineNoInitSequentialStatesOn($parentStateId, $childStateIds, ?HistoryType $historyType = null): void
    {
        throw new \RuntimeException('Not implements');
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::localTransition()
     */
    public function localTransition(int $priority = 0): LocalTransitionBuilder
    {
        throw new \RuntimeException('Not implements');
    }

    private function postConstructStateMachine(StateMachineImpl $stateMachine): void
    {
        if (isset($this->postConstructMethod)) {
            // TODO: invokde the post constractur
            // the method must be in the state machine implementation
        }
    }

    private function postProcessStateMachine(string $clz, $component)
    {
        if ($component != null) {
            // XXX:
            // $postProcessors = SquirrelPostProcessorProvider.getInstance().getCallablePostProcessors(clz);
            $postProcessors = [];
            foreach ($postProcessors as $postProcessor) {
                $postProcessor->postProcess($component);
            }
        }
        return $component;
    }

    /**
     * Checks if the state exists
     *
     * @param mixed $initialStateId
     * @return bool
     */
    private function isValidState($initialStateId): bool
    {
        return $this->states->offsetExists($initialStateId);
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::setScanAnnotations()
     */
    public function setScanAnnotations(bool $scanAnnotations): self
    {
        $this->scanAnnotations = $scanAnnotations;
        return $this;
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::setActionExecutionService()
     */
    public function setActionExecutionService(ActionExecutionService $actionExecutionService): self
    {
        $this->actionExecutionService = $actionExecutionService;
        return $this;
    }

    /**
     * Gets execution service
     *
     * @return ActionExecutionService
     */
    protected function getActionExecutionService(): ActionExecutionService
    {
        if (! isset($this->actionExecutionService)) {
            $container = $this->getContainer();
            $this->actionExecutionService = new ExecutionServiceImpl($container);
        }
        return $this->actionExecutionService;
    }

    /**
     *
     * {@inheritdoc}
     * @see \Pluf\Workflow\StateMachineBuilder::newStateMachine()
     */
    public function build($initialStateId, ?array $extraParams = null): StateMachine
    {
        $this->prepare();
        if (! $this->isValidState($initialStateId)) {
            throw new IllegalArgumentException("Cannot find Initial state \'" . $initialStateId . "\' in state machine.");
        }

        $configuration = $this->getStateMachineConfiguration();
        $actionExecutionService = $this->getActionExecutionService();

        // Just internal implementaion allowed
        $stateMachine = new StateMachineImpl($initialStateId, $this->states, $configuration);
        $stateMachine->setStartEvent($this->startEvent)
            ->setFinishEvent($this->finishEvent)
            ->setTerminateEvent($this->terminateEvent)
            ->setExtraParamTypes($this->extraParamTypes)
            ->setTypeOfContext($this->contextType)
            ->setTypeOfStateMachine($this->stateMachineImplClazz)
            ->setTypeOfState($this->stateType)
            ->setTypeOfEvent($this->eventType)
            ->setScriptManager($this->scriptManager)
            ->setActionExecutionService($actionExecutionService)
            ->setEntryPoint(true);

        // TODO: move to the state machin constructor
        $this->postConstructStateMachine($stateMachine);
        $this->postProcessStateMachine($this->stateMachineImplClazz, $stateMachine);
        return $stateMachine;
    }
}