blob: 7303185d0651502e5195515c1502831bdf108d8b [file] [log] [blame]
/**
* *******************************************************************************
* 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
}
}
}