| /** |
| * ******************************************************************************* |
| * Copyright (c) 2019 Robert Bosch GmbH and others. |
| * |
| * This program and the accompanying materials are made |
| * available under the terms of the Eclipse Public License 2.0 |
| * which is available at https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Robert Bosch GmbH - initial API and implementation |
| * ******************************************************************************* |
| */ |
| |
| package templates.m2m.utils |
| |
| import java.util.LinkedList |
| import java.util.List |
| import org.eclipse.app4mc.amalthea.model.AbstractEventChain |
| import org.eclipse.app4mc.amalthea.model.Event |
| import org.eclipse.app4mc.amalthea.model.EventChain |
| import org.eclipse.app4mc.amalthea.model.EventChainContainer |
| import org.eclipse.app4mc.amalthea.model.EventChainItem |
| import org.eclipse.app4mc.amalthea.model.EventChainReference |
| |
| class EventChainCrawler { |
| |
| static interface TestCondition{ |
| def boolean run(AbstractEventChain _eventChain) |
| } |
| |
| |
| //this list is used to identify cycles: chain1 references chain2, chain2 references chain1 |
| private var TestCondition testCondition |
| private var EventChain rootEventChain; |
| private var List<AbstractEventChain> traversedEventChains = new LinkedList |
| |
| new(TestCondition _testCondition){ |
| testCondition = _testCondition |
| } |
| |
| /** |
| * @brief |
| * Applies the test condition to the supplied event chain and its sub-elements (segments). |
| * The sequence of event chain references are parsed, to support ranges given by parent event chains. |
| * Otherwise no event sequence parsing is applied. Use function @parseEventChainSequence |
| * to analyze sequence on top level. |
| * |
| * @return true, if the element and its sub elements satisfy the test condition |
| * @note |
| */ |
| public def boolean evaluate(EventChain _eventChain){ |
| rootEventChain = _eventChain |
| return (evaluateElement(_eventChain)) |
| } |
| |
| private def boolean evaluateElement(AbstractEventChain _eventChain){ |
| if (!_eventChain.segments.isEmpty){ |
| for (EventChainItem eventChainItem : _eventChain.segments){ |
| if (eventChainItem instanceof EventChainReference){ |
| if(!processReference(eventChainItem as EventChainReference, _eventChain)){ |
| return false |
| } |
| } else if (eventChainItem instanceof EventChainContainer){ |
| if (!evaluateElement((eventChainItem as EventChainContainer).eventChain)){ |
| return false |
| } |
| } else { |
| return false; |
| } |
| } |
| } else { |
| if (!processLeafElement(_eventChain)){ |
| return false |
| } |
| } |
| return true |
| } |
| |
| |
| private def processLeafElement(AbstractEventChain _eventChain) { |
| if (testCondition.run(_eventChain)){ |
| traversedEventChains.add(_eventChain) |
| } else { |
| return false |
| } |
| } |
| |
| private def boolean processReference(EventChainReference _reference, AbstractEventChain _parentEventChain) { |
| val eventSequenceCrawler = new EventChainCrawler(testCondition) |
| //referenced event chain's elements fullfill test condition? |
| if (!eventSequenceCrawler.evaluate(_reference.eventChain)) |
| return false |
| if (!eventSequenceCrawler.applyOrder) |
| return false |
| if (!eventSequenceCrawler.applyBoundaries(_parentEventChain.stimulus, _parentEventChain.response)) |
| return false |
| eventSequenceCrawler.traversedEventChains.forEach[traversedEventChains.add(it)] |
| return true |
| } |
| |
| |
| public def List<AbstractEventChain> getTraversedEventChains(){ |
| return traversedEventChains |
| } |
| |
| /** |
| * @brief order event chain (with segments) by concatenation of sub-event chains' stimulus/response pairs |
| * @description |
| * order event chains by concatenation of event chain stimulus/response pair |
| * - example event chain with segments: |
| * EC1(stim=A, resp=B), EC2(stim=C, resp=D), EC3(stim=B, resp=C) |
| * - results in: EC1, EC3, EC2 due to (stim=A, [resp=B)(stim=B], [resp=C)(stim=C], resp=D) |
| * |
| * @return true, if sequence has been parsed |
| * |
| */ |
| public def boolean applyOrder(){ |
| |
| if (traversedEventChains === null ){ |
| return false |
| } |
| //EC contains only one node --> sequence is rather obvious |
| if (traversedEventChains.size <= 1){ |
| return true |
| } |
| |
| var LinkedList<AbstractEventChain> sortedList = new LinkedList |
| // if there is more than one element in traversed nodes list, sorting is required |
| sortedList.add(traversedEventChains.get(0)) |
| traversedEventChains.remove(0) |
| |
| while (!traversedEventChains.isEmpty ){ |
| var sortedListSizeBefore =sortedList.size |
| for (AbstractEventChain unsortedEvent : traversedEventChains){ |
| //if feasible, add investigated event at head or tail of sorted list |
| if (unsortedEvent.response == sortedList.get(0).stimulus){ |
| sortedList.addFirst(unsortedEvent) |
| } |
| if (unsortedEvent.stimulus == sortedList.last.response){ |
| sortedList.addLast(unsortedEvent) |
| } |
| } |
| traversedEventChains.removeAll(sortedList) |
| |
| var sortedListSizeAfter = sortedList.size |
| if(sortedListSizeAfter === sortedListSizeBefore){ |
| traversedEventChains = sortedList |
| return false |
| } |
| |
| } |
| traversedEventChains = sortedList |
| return true |
| } |
| |
| public def boolean applyBoundaries(Event _initialStimulus, Event _finalResponse) { |
| //parent specifies a scope that ends at referenced event chains, or starts at end of reference |
| //in both cases the reference does not contribute to parent event chain |
| if (traversedEventChains.isEmpty) |
| return true |
| if (traversedEventChains.get(0).stimulus===_finalResponse) { |
| traversedEventChains.clear |
| return true |
| } |
| if (traversedEventChains.last.response===_initialStimulus){ |
| traversedEventChains.clear |
| return true |
| } |
| |
| var startIndex = 0 |
| var endIndex = traversedEventChains.size-1 |
| for(index:0..<traversedEventChains.size){ |
| if (traversedEventChains.get(index).stimulus===_initialStimulus) { |
| startIndex=index |
| } |
| if (traversedEventChains.get(index).response===_finalResponse){ |
| endIndex=index |
| } |
| } |
| if (startIndex <= endIndex){ |
| //add all referenced event chain elements between start and end index to parent |
| traversedEventChains = traversedEventChains.subList(startIndex, endIndex+1) |
| return true |
| }else{ |
| //parent specifies an illegal range, as the final event chain element occurs prior to the start in referenced event chain |
| //--> nothing is added to parent chain |
| return false |
| } |
| } |
| |
| |
| |
| } |