ebi_bpmn 0.0.44

A BPMN parser, writer and executor
Documentation
use crate::{
    BusinessProcessModelAndNotation,
    element::BPMNElementTrait,
    if_not::IfNotDefault,
    marking::{BPMNRootMarking, BPMNSubMarking, Token},
    parser::parser_state::GlobalIndex,
    semantics::TransitionIndex,
    traits::{
        objectable::{BPMNObject, EMPTY_FLOWS},
        processable::Processable,
        transitionable::{
            Transitionable, enabledness_xor_join_only, execute_transition_parallel_split,
            execute_transition_xor_join_consume, number_of_transitions_xor_join_only,
            transition_2_consumed_tokens_xor_join, transition_2_produced_tokens_concurrent_split,
        },
    },
};
use anyhow::{Result, anyhow};
use bitvec::{bitvec, vec::BitVec};
use ebi_activity_key::Activity;
use ebi_arithmetic::{Fraction, One};

#[derive(Debug, Clone)]
pub struct BPMNMessageIntermediateCatchEvent {
    pub(crate) global_index: GlobalIndex,
    pub(crate) id: String,
    pub(crate) local_index: usize,
    pub(crate) message_marker_id: Option<String>,
    pub(crate) incoming_sequence_flows: Vec<usize>,
    pub(crate) outgoing_sequence_flows: Vec<usize>,
    pub(crate) incoming_message_flow: Option<usize>,
}

impl BPMNElementTrait for BPMNMessageIntermediateCatchEvent {
    fn add_incoming_sequence_flow(&mut self, flow_index: usize) -> Result<()> {
        self.incoming_sequence_flows.push(flow_index);
        Ok(())
    }

    fn add_outgoing_sequence_flow(&mut self, flow_index: usize) -> Result<()> {
        self.outgoing_sequence_flows.push(flow_index);
        Ok(())
    }

    fn add_incoming_message_flow(&mut self, flow_index: usize) -> Result<()> {
        if self.incoming_message_flow.is_some() {
            return Err(anyhow!("cannot add a second incoming message flow"));
        }
        self.incoming_message_flow = Some(flow_index);
        Ok(())
    }

    fn add_outgoing_message_flow(&mut self, _flow_index: usize) -> Result<()> {
        Err(anyhow!(
            "message intermediate catch events cannot have outgoing message flows"
        ))
    }

    fn verify_structural_correctness(
        &self,
        _parent: &dyn Processable,
        _bpmn: &BusinessProcessModelAndNotation,
    ) -> Result<()> {
        Ok(())
    }
}

impl BPMNObject for BPMNMessageIntermediateCatchEvent {
    fn local_index(&self) -> usize {
        self.local_index
    }

    fn global_index(&self) -> GlobalIndex {
        self.global_index
    }

    fn activity(&self) -> Option<Activity> {
        None
    }

    fn id(&self) -> &str {
        &self.id
    }

    fn is_unconstrained_start_event(
        &self,
        _bpmn: &BusinessProcessModelAndNotation,
    ) -> Result<bool> {
        Ok(false)
    }

    fn is_end_event(&self) -> bool {
        false
    }

    fn incoming_sequence_flows(&self) -> &[usize] {
        &self.incoming_sequence_flows
    }

    fn outgoing_sequence_flows(&self) -> &[usize] {
        &self.outgoing_sequence_flows
    }

    fn incoming_message_flows(&self) -> &[usize] {
        self.incoming_message_flow.as_slice()
    }

    fn outgoing_message_flows(&self) -> &[usize] {
        &EMPTY_FLOWS
    }

    fn can_start_process_instance(&self, bpmn: &BusinessProcessModelAndNotation) -> Result<bool> {
        if self.incoming_sequence_flows.len() >= 1 {
            //if it has an incoming sequence flow, it cannot start a process instance
            Ok(false)
        } else if let Some(message_flow_index) = self.incoming_message_flow {
            let source = bpmn.message_flow_index_2_source(message_flow_index)?;
            if source.outgoing_message_flows_always_have_tokens() {
                //a message from a collapsed pool is always there
                Ok(true)
            } else {
                //otherwise, the message must be there = the instance has already started
                Ok(false)
            }
        } else {
            //there is no constraining message, so this message start event can start a process instance
            Ok(true)
        }
    }

    fn outgoing_message_flows_always_have_tokens(&self) -> bool {
        false
    }

    fn outgoing_messages_cannot_be_removed(&self) -> bool {
        false
    }

    fn incoming_messages_are_ignored(&self) -> bool {
        false
    }

    fn can_have_incoming_sequence_flows(&self) -> bool {
        true
    }

    fn can_have_outgoing_sequence_flows(&self) -> bool {
        true
    }
}

impl Transitionable for BPMNMessageIntermediateCatchEvent {
    fn number_of_transitions(&self, _marking: &BPMNSubMarking) -> usize {
        number_of_transitions_xor_join_only!(self)
    }

    fn enabled_transitions(
        &self,
        root_marking: &BPMNRootMarking,
        sub_marking: &BPMNSubMarking,
        _parent: &dyn Processable,
        bpmn: &BusinessProcessModelAndNotation,
    ) -> Result<BitVec> {
        //check whether the message is present
        if let Some(message_flow_index) = self.incoming_message_flow {
            //there is a connected message flow
            let source = bpmn.message_flow_index_2_source(message_flow_index)?;
            if !source.outgoing_message_flows_always_have_tokens() {
                //this message must actually be there

                if root_marking.message_flow_2_tokens[message_flow_index] == 0 {
                    //message is not present; all transitions are not enabled
                    return Ok(bitvec![0; self.incoming_sequence_flows.len()]);
                }
            } else {
                //if the message flow has always tokens, we do not need to check them
            }
        } else {
            //if there is no incoming message flow, we assume there is always a message
        }

        //see which transitions are enabled
        Ok(enabledness_xor_join_only!(self, sub_marking))
    }

    fn execute_transition(
        &self,
        transition_index: TransitionIndex,
        root_marking: &mut BPMNRootMarking,
        sub_marking: &mut BPMNSubMarking,
        parent: &dyn Processable,
        bpmn: &BusinessProcessModelAndNotation,
    ) -> Result<()> {
        //consume token
        if let Some(sequence_flow_index) = self.incoming_sequence_flows.iter().next() {
            let sequence_flow = &parent.sequence_flows_non_recursive()[*sequence_flow_index];
            let source = &parent.elements_non_recursive()[sequence_flow.source_local_index];
            if source.is_event_based_gateway() {
                //special case: source is an event-based gateway

                //remove a token from all outgoing sequence flows of the event-based gateway
                for outgoing_sequence_flow in source.outgoing_sequence_flows() {
                    sub_marking.sequence_flow_2_tokens[*outgoing_sequence_flow] -= 1;
                }
            } else {
                //not a special case
                execute_transition_xor_join_consume!(self, sub_marking, transition_index);
            }
        } else {
            //not a special case
            execute_transition_xor_join_consume!(self, sub_marking, transition_index);
        }

        //consume message
        if let Some(message_flow_index) = self.incoming_message_flow {
            //there is a connected message flow
            let source = bpmn.message_flow_index_2_source(message_flow_index)?;
            if !source.outgoing_message_flows_always_have_tokens() {
                //this message must actually be there
                if !source.outgoing_messages_cannot_be_removed() {
                    root_marking.message_flow_2_tokens[message_flow_index] -= 1;
                }
            } else {
                //if the message flow has always tokens, we do not need to check them
            }
        } else {
            //if there is no incoming message flow, we assume there is always a message
        }

        //produce
        execute_transition_parallel_split!(self, sub_marking);
        Ok(())
    }

    fn transition_activity(
        &self,
        _transition_index: TransitionIndex,
        _marking: &BPMNSubMarking,
    ) -> Option<Activity> {
        None
    }

    fn transition_debug(
        &self,
        transition_index: TransitionIndex,
        _marking: &BPMNSubMarking,
        _bpmn: &BusinessProcessModelAndNotation,
    ) -> Option<String> {
        Some(format!(
            "message intermediate catch event `{}`; internal transition {}",
            self.id, transition_index
        ))
    }

    fn transition_probabilistic_penalty(
        &self,
        _transition_index: TransitionIndex,
        _marking: &BPMNSubMarking,
        _parent: &dyn Processable,
    ) -> Option<Fraction> {
        Some(Fraction::one())
    }

    fn transition_2_consumed_tokens(
        &self,
        transition_index: TransitionIndex,
        _root_marking: &BPMNRootMarking,
        _sub_marking: &BPMNSubMarking,
        parent: &dyn Processable,
        bpmn: &BusinessProcessModelAndNotation,
    ) -> Result<Vec<Token>> {
        let mut result =
            if let Some(sequence_flow_index) = self.incoming_sequence_flows.iter().next() {
                let sequence_flow = &parent.sequence_flows_non_recursive()[*sequence_flow_index];
                let source = &parent.elements_non_recursive()[sequence_flow.source_local_index];
                if source.is_event_based_gateway() {
                    //special case: source is an event-based gateway

                    //remove a token from all outgoing sequence flows of the event-based gateway
                    let mut result = Vec::with_capacity(source.outgoing_sequence_flows().len());
                    for sequence_flow_local_index in source.outgoing_sequence_flows() {
                        let sequence_flow = parent
                            .sequence_flows_non_recursive()
                            .get(*sequence_flow_local_index)
                            .and_if_not_error_default()?;
                        result.push(Token::SequenceFlow(sequence_flow.global_index));
                    }
                    result
                } else {
                    //not a special case
                    transition_2_consumed_tokens_xor_join!(self, transition_index, parent)
                }
            } else {
                //not a special case
                transition_2_consumed_tokens_xor_join!(self, transition_index, parent)
            };

        //message
        if let Some(message_flow_index) = self.incoming_message_flow {
            //there is a connected message flow
            let source = bpmn.message_flow_index_2_source(message_flow_index)?;
            if !source.outgoing_message_flows_always_have_tokens() {
                //this message must actually be there
                if !source.outgoing_messages_cannot_be_removed() {
                    let message_flow = bpmn
                        .message_flows
                        .get(message_flow_index)
                        .and_if_not_error_default()?;
                    result.push(Token::MessageFlow(message_flow.global_index))
                }
            } else {
                //if the message flow has always tokens, we do not need to check them
            }
        } else {
            //if there is no incoming message flow, we assume there is always a message
        }

        Ok(result)
    }

    fn transition_2_produced_tokens(
        &self,
        _transition_index: TransitionIndex,
        _root_marking: &BPMNRootMarking,
        _sub_marking: &BPMNSubMarking,
        parent: &dyn Processable,
        _bpmn: &BusinessProcessModelAndNotation,
    ) -> Result<Vec<Token>> {
        Ok(transition_2_produced_tokens_concurrent_split!(self, parent))
    }
}