/*******************************************************************************
* Copyright (c) 2016 Ecole Polytechnique de Montreal, Ericsson
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
******************************************************************************/
package org.eclipse.tracecompass.internal.tmf.analysis.xml.core.model;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.common.core.NonNullUtils;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.module.IXmlStateSystemContainer;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.pattern.stateprovider.XmlPatternStateProvider;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.stateprovider.TmfXmlStrings;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
/**
* This Class implements a pattern handler tree in the XML-defined state system.
* It receives events and dispatches it to Active finite state machines.
*
* @author Jean-Christian Kouame
*/
public class TmfXmlPatternEventHandler {
/* list of states changes */
private final XmlPatternStateProvider fParent;
private final List<String> fInitialFsm;
private final Map<String, TmfXmlTransitionValidator> fTestMap;
private final Map<String, ITmfXmlAction> fActionMap;
private final Map<String, TmfXmlFsm> fFsmMap = new LinkedHashMap<>();
private final List<TmfXmlFsm> fActiveFsmList = new ArrayList<>();
/**
* Constructor
*
* @param modelFactory
* The factory used to create XML model elements
* @param node
* The XML root of this event handler
* @param parent
* The state system container this event handler belongs to
*/
public TmfXmlPatternEventHandler(ITmfXmlModelFactory modelFactory, Element node, IXmlStateSystemContainer parent) {
fParent = (XmlPatternStateProvider) parent;
String initialFsm = node.getAttribute(TmfXmlStrings.INITIAL);
fInitialFsm = initialFsm.isEmpty() ? Collections.EMPTY_LIST : Arrays.asList(initialFsm.split(TmfXmlStrings.AND_SEPARATOR));
Map<String, TmfXmlTransitionValidator> testMap = new HashMap<>();
NodeList nodesTest = node.getElementsByTagName(TmfXmlStrings.TEST);
/* load transition input */
for (int i = 0; i < nodesTest.getLength(); i++) {
Element element = (Element) nodesTest.item(i);
if (element == null) {
throw new IllegalArgumentException();
}
TmfXmlTransitionValidator test = modelFactory.createTransitionValidator(element, fParent);
testMap.put(test.getId(), test);
}
fTestMap = Collections.unmodifiableMap(testMap);
@NonNull Builder<String, ITmfXmlAction> builder = ImmutableMap.builder();
NodeList nodesAction = node.getElementsByTagName(TmfXmlStrings.ACTION);
/* load actions */
for (int i = 0; i < nodesAction.getLength(); i++) {
Element element = (Element) nodesAction.item(i);
if (element == null) {
throw new IllegalArgumentException();
}
TmfXmlAction action = modelFactory.createAction(element, fParent);
builder.put(action.getId(), action);
}
builder.put(TmfXmlStrings.CONSTANT_PREFIX + ITmfXmlAction.CLEAR_STORED_FIELDS_STRING, new ResetStoredFieldsAction(fParent));
builder.put(TmfXmlStrings.CONSTANT_PREFIX + ITmfXmlAction.SAVE_STORED_FIELDS_STRING, new UpdateStoredFieldsAction(fParent));
fActionMap = builder.build();
NodeList nodesFsm = node.getElementsByTagName(TmfXmlStrings.FSM);
/* load fsm */
for (int i = 0; i < nodesFsm.getLength(); i++) {
Element element = (Element) nodesFsm.item(i);
if (element == null) {
throw new IllegalArgumentException();
}
TmfXmlFsm fsm = modelFactory.createFsm(element, fParent);
fFsmMap.put(fsm.getId(), fsm);
}
}
/**
* Start a new scenario for this specific fsm id. If the fsm support only a
* single instance and this instance already exist, no new scenario is then
* started. If the scenario is created we handle the current event directly.
*
* @param fsmIds
* The IDs of the fsm to start
* @param event
* The current event
* @param force
* True to force the creation of the scenario, false otherwise
*/
public void startScenario(List<String> fsmIds, @Nullable ITmfEvent event, boolean force) {
for (String fsmId : fsmIds) {
TmfXmlFsm fsm = NonNullUtils.checkNotNull(fFsmMap.get(fsmId));
if (!fActiveFsmList.contains(fsm)) {
fActiveFsmList.add(fsm);
}
fsm.createScenario(event, this, force);
}
}
/**
* Get all the defined transition tests
*
* @return The tests in a map
*/
public Map<String, TmfXmlTransitionValidator> getTestMap() {
return fTestMap;
}
/**
* Get all the defined actions
*
* @return The actions
*/
public Map<String, ITmfXmlAction> getActionMap() {
return fActionMap;
}
/**
* If the pattern handler can handle the event, it send the event to all
* finite state machines with ongoing scenarios
*
* @param event
* The trace event to handle
*/
public void handleEvent(ITmfEvent event) {
/*
* Order is important so cannot be parallelized
*/
final @NonNull List<@NonNull TmfXmlFsm> activeFsmList = fActiveFsmList;
final @NonNull Map<@NonNull String, @NonNull TmfXmlFsm> fsmMap = fFsmMap;
if (activeFsmList.isEmpty()) {
List<String> fsmIds = fInitialFsm;
if (fsmIds.isEmpty()) {
fsmIds = new ArrayList<>();
for (TmfXmlFsm fsm : fsmMap.values()) {
fsmIds.add(fsm.getId());
}
}
if (!fsmIds.isEmpty()) {
startScenario(fsmIds, null, true);
}
} else {
List<String> fsmToStart = new ArrayList<>();
for (Map.Entry<String, TmfXmlFsm> entry : fsmMap.entrySet()) {
if (entry.getValue().isNewScenarioAllowed()) {
fsmToStart.add(entry.getKey());
}
}
if (!fsmToStart.isEmpty()) {
startScenario(fsmToStart, null, false);
}
}
for (TmfXmlFsm fsm : activeFsmList) {
fsm.handleEvent(event, fTestMap);
}
}
/**
* Abandon all the ongoing scenarios
*/
public void dispose() {
for (TmfXmlFsm fsm : fActiveFsmList) {
fsm.dispose();
}
}
/**
* Get the fsm corresponding to the specified id
*
* @param fsmId
* The id of the fsm
* @return The fsm found, null if nothing found
*/
public @Nullable TmfXmlFsm getFsm(String fsmId) {
return fFsmMap.get(fsmId);
}
}