// Copyright 2012 Georg-August-Universität Göttingen, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package de.ugoe.cs.autoquest.plugin.uml;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.emf.common.util.EList;
import org.eclipse.uml2.uml.Association;
import org.eclipse.uml2.uml.Class;
import org.eclipse.uml2.uml.Comment;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Interaction;
import org.eclipse.uml2.uml.Lifeline;
import org.eclipse.uml2.uml.Message;
import org.eclipse.uml2.uml.MessageOccurrenceSpecification;
import org.eclipse.uml2.uml.Model;
import org.eclipse.uml2.uml.Operation;
import org.eclipse.uml2.uml.Region;
import org.eclipse.uml2.uml.StateMachine;
import org.eclipse.uml2.uml.Transition;
import org.eclipse.uml2.uml.UMLPackage;
import org.eclipse.uml2.uml.Vertex;
import de.ugoe.cs.autoquest.eventcore.Event;
import de.ugoe.cs.autoquest.plugin.http.eventcore.SOAPEventType;
import de.ugoe.cs.autoquest.plugin.uml.eventcore.UMLTransitionType;
/**
*
* Utilities for working with UML.
*
*
* @author Steffen Herbold
*/
public class UMLUtils {
/**
*
* Creates a sequence of events with {@link UMLTransitionType} as event type from a given
* sequence of events with the {@link SOAPEventType}, by matching the sequences to a state
* machine.
*
*
* @param sequence
* SOAP sequences
* @param stateMachine
* the state machine
* @return create UML sequences
*/
public static List createUMLTransitionSequence(List sequence,
StateMachine stateMachine)
{
List> matchingSequences =
determineMatchingTransitionSequences(sequence, stateMachine);
if (matchingSequences.size() != 1) {
throw new RuntimeException("no unique match found; " + matchingSequences.size() +
" matches");
}
List umlEventSequence = new LinkedList<>();
for (Transition transition : matchingSequences.get(0)) {
umlEventSequence.add(new Event(new UMLTransitionType(transition)));
}
return umlEventSequence;
}
/**
*
* Uses a sequences of events with the {@link UMLTransitionType} to determine the transition
* probabilities for the state machine.
*
*
* @param sequences
* UML sequences
* @param stateMachine
* state machine to be converted to a usage profile
*/
public static void convertStateMachineToUsageProfile(Collection> sequences,
StateMachine stateMachine)
{
// create state->outgoings hashmap
Map> stateMap = new HashMap<>();
for (Region region : stateMachine.getRegions()) {
for (Vertex state : region.getSubvertices()) {
stateMap.put(state, new HashMap());
}
}
// create counters for each transition
for (List sequence : sequences) {
for (Event event : sequence) {
if (event.getType() instanceof UMLTransitionType) {
Transition transition = ((UMLTransitionType) event.getType()).getTransition();
Map transitionMap = stateMap.get(transition.getSource());
Integer value = transitionMap.get(transition);
if (value == null) {
value = 0;
}
transitionMap.put(transition, value + 1);
}
else {
throw new RuntimeException(
"Wrong event type. Only UMLTransitionType supported but was: " +
event.getType().getClass().getName());
}
}
}
// calculate probabilities
for (Region region : stateMachine.getRegions()) {
for (Vertex state : region.getSubvertices()) {
Map transitionMap = stateMap.get(state);
int totalCount = 0;
for (Entry entry : transitionMap.entrySet()) {
totalCount += entry.getValue();
}
if (totalCount != 0) {
for (Transition transition : state.getOutgoings()) {
double prob = 0.0d;
if (transitionMap.containsKey(transition)) {
prob = ((double) transitionMap.get(transition)) / totalCount;
}
Comment comment = transition.createOwnedComment();
comment.setBody("" + prob);
}
}
else {
// system has never been in this state, all transitions equally likely
int numOutgoings = state.getOutgoings().size();
for (Transition transition : state.getOutgoings()) {
Comment comment = transition.createOwnedComment();
comment.setBody("" + (1.0d / numOutgoings));
}
}
}
}
}
/**
*
* Determines all matching {@link Transition} sequences in a state machine for a given sequence
* of SOAP events.
*
*
* @param sequence
* SOAP sequence
* @param stateMachine
* the state machine
* @return all matching {@link Transition} sequences
*/
public static List> determineMatchingTransitionSequences(List sequence,
StateMachine stateMachine)
{
EList regions = stateMachine.getRegions();
EList states = null;
for (Region region : regions) {
if (states == null) {
states = region.getSubvertices();
}
else {
states.addAll(region.getSubvertices());
}
}
List allTransitions = new LinkedList<>();
for (Vertex state : states) {
allTransitions.addAll(state.getOutgoings());
}
List> matchingSequences = null;
List currentTransitions = null;
// first, we try to find a single unique transition that we can match using the method name
for (Iterator eventIterator = sequence.iterator(); eventIterator.hasNext();) {
Event event = eventIterator.next();
if (event.getType() instanceof SOAPEventType) {
SOAPEventType eventType = (SOAPEventType) event.getType();
if (matchingSequences == null) {
matchingSequences = new LinkedList<>();
List initialMatches = matchTransitions(allTransitions, eventType);
for (Transition transition : initialMatches) {
List candidate = new LinkedList<>();
candidate.add(transition);
matchingSequences.add(candidate);
}
currentTransitions = initialMatches;
}
else {
List> nextMatchingSequences = new LinkedList<>();
List nextCurrentTransitions = new LinkedList<>();
Iterator currentTransitionIterator = currentTransitions.iterator();
Iterator> currentMatchingSequencesIterator =
matchingSequences.iterator();
while (currentTransitionIterator.hasNext()) {
Transition currentTransition = currentTransitionIterator.next();
List currentMatch = currentMatchingSequencesIterator.next();
List matches =
matchTransitions(currentTransition.getTarget().getOutgoings(),
eventType);
if (matches.isEmpty()) {
throw new RuntimeException("no matches found");
}
for (Transition matchingTransition : matches) {
List candidate = new LinkedList<>(currentMatch);
candidate.add(matchingTransition);
nextMatchingSequences.add(candidate);
nextCurrentTransitions.add(matchingTransition);
}
}
matchingSequences = nextMatchingSequences;
currentTransitions = nextCurrentTransitions;
}
}
else {
throw new RuntimeException(
"Wrong event type. Only UMLTransitionType supported but was: " +
event.getType().getClass().getName());
}
}
return matchingSequences;
}
/**
*
* Extends a given model with an interaction that represents an observed sequence.
*
*
* @param sequence
* sequence that is added as sequence diagram
* @param model
* UML model to which the interaction is added
* @param interactionName
* name of the interaction
*/
public static void createInteractionFromEventSequence(List sequence,
Model model,
String interactionName)
{
Map classMap = new HashMap<>();
EList elements = model.getOwnedElements();
for (Element element : elements) {
if (element instanceof Class) {
classMap.put(((Class) element).getName(), (Class) element);
}
}
Interaction interaction =
(Interaction) model.createPackagedElement(interactionName,
UMLPackage.Literals.INTERACTION);
Lifeline userLifeline = interaction.createLifeline("user");
int i = 0;
for (Event event : sequence) {
if (!(event.equals(Event.STARTEVENT) || event.equals(Event.ENDEVENT))) {
if (event.getType() instanceof SOAPEventType) {
SOAPEventType eventType = (SOAPEventType) event.getType();
String serviceName = eventType.getServiceName();
String methodName = eventType.getCalledMethod();
Class targetClass = classMap.get(serviceName);
if (targetClass == null) {
throw new RuntimeException(
"Could not find class in the UML model that belong to the service: " +
serviceName);
}
Lifeline targetLifeline = interaction.getLifeline(serviceName);
if (targetLifeline == null) {
targetLifeline = interaction.createLifeline(serviceName);
Association association =
(Association) model.getPackagedElement("user_" + serviceName, true,
UMLPackage.Literals.ASSOCIATION,
true);
targetLifeline.setRepresents(association.getMemberEnd(serviceName, classMap
.get(serviceName)));
}
MessageOccurrenceSpecification sendFragment =
(MessageOccurrenceSpecification) interaction
.createFragment(i + ":" + methodName + "_sendFragment",
UMLPackage.Literals.MESSAGE_OCCURRENCE_SPECIFICATION);
MessageOccurrenceSpecification recvFragment =
(MessageOccurrenceSpecification) interaction
.createFragment(i + ":" + methodName + "_recvFragment",
UMLPackage.Literals.MESSAGE_OCCURRENCE_SPECIFICATION);
sendFragment.setCovered(userLifeline);
recvFragment.setCovered(targetLifeline);
Message message = interaction.createMessage(methodName);
message.setSignature(getOperationFromName(targetClass.getOperations(),
methodName));
message.setSendEvent(sendFragment);
message.setReceiveEvent(recvFragment);
sendFragment.setMessage(message);
recvFragment.setMessage(message);
}
else {
throw new RuntimeException(
"Wrong event type. Only SOAPEventType supported but was: " +
event.getType().getClass().getName());
}
i++;
}
}
}
/**
*
* Fetches an operation using only its name from a list of operations. Returns the first match
* found or null if no match is found.
*
*
* @param operations
* list of operations
* @param name
* name of the operation
* @return first matching operation; null if no match is found
*/
private static Operation getOperationFromName(EList operations, String name) {
if (name == null) {
throw new IllegalArgumentException("name of the operation must not be null");
}
if (operations != null) {
for (Operation operation : operations) {
if (operation.getName() != null && operation.getName().equals(name)) {
return operation;
}
}
}
return null;
}
/**
*
* Determines which transitions match a given {@link SOAPEventType}.
*
*
* @param transitions
* the transitions
* @param eventType
* the SOAP event
* @return matching transitions
*/
private static List matchTransitions(List transitions,
SOAPEventType eventType)
{
List matching = new LinkedList<>();
for (Transition transition : transitions) {
// String serviceName = transition.getName().split("\\.")[0]; // TODO service name check
String methodName = transition.getName().split("\\.")[1];
if (methodName.equals(eventType.getCalledMethod())) {
matching.add(transition);
}
}
return matching;
}
}