// 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.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import org.eclipse.emf.common.util.EList;
import org.eclipse.uml2.uml.Activity;
import org.eclipse.uml2.uml.ActivityEdge;
import org.eclipse.uml2.uml.ActivityNode;
import org.eclipse.uml2.uml.CallConcurrencyKind;
import org.eclipse.uml2.uml.CallEvent;
import org.eclipse.uml2.uml.CallOperationAction;
import org.eclipse.uml2.uml.Comment;
import org.eclipse.uml2.uml.Component;
import org.eclipse.uml2.uml.Connector;
import org.eclipse.uml2.uml.ConnectorEnd;
import org.eclipse.uml2.uml.DataType;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Expression;
import org.eclipse.uml2.uml.InstanceSpecification;
import org.eclipse.uml2.uml.InstanceValue;
import org.eclipse.uml2.uml.Interaction;
import org.eclipse.uml2.uml.InteractionFragment;
import org.eclipse.uml2.uml.Interface;
import org.eclipse.uml2.uml.Lifeline;
import org.eclipse.uml2.uml.LiteralBoolean;
import org.eclipse.uml2.uml.LiteralInteger;
import org.eclipse.uml2.uml.LiteralNull;
import org.eclipse.uml2.uml.LiteralReal;
import org.eclipse.uml2.uml.LiteralString;
import org.eclipse.uml2.uml.Message;
import org.eclipse.uml2.uml.MessageOccurrenceSpecification;
import org.eclipse.uml2.uml.MessageSort;
import org.eclipse.uml2.uml.Model;
import org.eclipse.uml2.uml.NamedElement;
import org.eclipse.uml2.uml.Operation;
import org.eclipse.uml2.uml.Package;
import org.eclipse.uml2.uml.Parameter;
import org.eclipse.uml2.uml.ParameterDirectionKind;
import org.eclipse.uml2.uml.Port;
import org.eclipse.uml2.uml.PrimitiveType;
import org.eclipse.uml2.uml.Property;
import org.eclipse.uml2.uml.Region;
import org.eclipse.uml2.uml.Slot;
import org.eclipse.uml2.uml.StateMachine;
import org.eclipse.uml2.uml.Stereotype;
import org.eclipse.uml2.uml.Transition;
import org.eclipse.uml2.uml.Trigger;
import org.eclipse.uml2.uml.UMLPackage;
import org.eclipse.uml2.uml.Vertex;
import de.ugoe.cs.autoquest.eventcore.Event;
import de.ugoe.cs.autoquest.eventcore.EventUtils;
import de.ugoe.cs.autoquest.plugin.http.SOAPUtils;
import de.ugoe.cs.autoquest.plugin.http.eventcore.SOAPEventType;
import de.ugoe.cs.autoquest.plugin.http.eventcore.SimpleSOAPEventType;
import de.ugoe.cs.autoquest.plugin.http.eventcore.SimpleSOAPEventType.CallType;
import de.ugoe.cs.autoquest.plugin.uml.eventcore.UMLTransitionType;
import de.ugoe.cs.autoquest.usageprofiles.IStochasticProcess;
import de.ugoe.cs.util.StringTools;
import de.ugoe.cs.util.console.Console;
/**
*
* Utilities for working with UML.
*
*
* @author Steffen Herbold
*/
public class UMLUtils {
/**
* In case a multiplicity is defined as *, this value defines the highest one that can be picked
*/
final static int MAX_MULTIPLICITY = 10;
/**
*
* Method for checking if the information in a usage journal can be mapped to the SUT model. In
* case this is not possible, the violations are reported.
*
*
* @param sequences
* sequences of the usage journal
* @param model
* SUT model that is validated
* @param testContextName
* name of the test context to be used; if null, the first test context found is used
* @return number of violations
*/
public static int validateModelWithLog(Collection> sequences,
Model model,
String testContextName)
{
int violationCount = 0;
Component testContext = fetchTestContext(model, testContextName);
if (testContext == null) {
violationCount++;
if (testContextName == null) {
Console.traceln(Level.SEVERE, "Could not find any TestContext in the model.");
}
else {
Console.traceln(Level.SEVERE, "Could not find TestContext in the model: " +
testContextName);
}
Console
.traceln(Level.SEVERE,
"Hint: Check if you have applied the TestContext stereotype correctly in the model.");
Console.traceln(Level.SEVERE, "Aborting");
return violationCount;
}
// Create list of unique methods calls
HashMap> calledMethods = new HashMap<>();
for (List sequence : sequences) {
for (Event event : sequence) {
String serviceName = SOAPUtils.getServiceNameFromEvent(event);
String calledMethod = SOAPUtils.getCalledMethodFromEvent(event);
if (serviceName != null) {
Set curCalledMethods = calledMethods.get(serviceName);
if (curCalledMethods == null) {
curCalledMethods = new TreeSet<>();
calledMethods.put(serviceName, curCalledMethods);
}
curCalledMethods.add(calledMethod);
}
}
}
Console.traceln(Level.INFO,
"Found the following services and operations in the usage data: ");
for (Entry> entry : calledMethods.entrySet()) {
Console.traceln(Level.INFO, "\tService \"" + entry.getKey() + "\": ");
for (String method : entry.getValue()) {
Console.traceln(Level.INFO, "\t\t" + method);
}
}
// fetch all SUTs and TestComponents
HashMap properties = new HashMap<>();
for (Property property : fetchAllSUTProperties(testContext)) {
properties.put(property.getName(), property);
}
for (Property property : fetchAllTestComponentProperties(testContext)) {
properties.put(property.getName(), property);
}
Console.traceln(Level.INFO, "Found the following services in the TestConfiguration:");
for (Entry entry : properties.entrySet()) {
Console.traceln(Level.INFO, "\t" + entry.getKey());
}
for (Entry> entry : calledMethods.entrySet()) {
String serviceName = entry.getKey();
Console.traceln(Level.INFO, "Checking service: " + serviceName);
Set methodNames = entry.getValue();
Property property = properties.get(serviceName);
if (property == null) {
violationCount++;
Console.traceln(Level.SEVERE, "\tCould not find property for service: " +
serviceName);
Console
.traceln(Level.SEVERE,
"\tHint: Check service name map and/or model if the service is present and spelled correctly.");
Console
.traceln(Level.SEVERE,
"\tHint: Check if the SUT/TestComponent stereotype has been applied correctly in this TestContext.");
}
else {
Set interfaces = getRealizedInterfacesFromProperty(property);
if (interfaces.isEmpty()) {
violationCount++;
Console
.traceln(Level.SEVERE,
"\tCould not find any interfaces implementing the property for service: " +
serviceName);
Console
.traceln(Level.SEVERE,
"\tHint: Check if the property correctly realizes the interfaces in the model.");
}
else {
Console.traceln(Level.INFO,
"\tFound the following realized interfaces for the service \"" +
serviceName + "\": ");
for (Interface intface : interfaces) {
Console.traceln(Level.INFO, "\t" + intface.getName());
for (Operation operation : intface.getAllOperations()) {
Console.traceln(Level.INFO, "\t\t" + operation.getName());
}
}
for (String methodName : methodNames) {
boolean methodFound = false;
for (Interface intface : interfaces) {
if (getOperationFromName(intface.getOperations(), methodName) != null) {
// interface found
Console.traceln(Level.INFO, "\tMethod " + methodName +
" found in interface " + intface.getName());
methodFound = true;
}
}
if (!methodFound) {
violationCount++;
Console.traceln(Level.SEVERE, "\tCould not find operation: " +
methodName);
}
}
}
}
}
return violationCount;
}
/**
*
* Creates a sequence of events with {@link UMLTransitionType} as event type from a given
* sequence of events with the {@link SOAPEventType} or {@link SimpleSOAPEventType}, 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 (matchingSequences == null) {
matchingSequences = new LinkedList<>();
List initialMatches = matchTransitions(allTransitions, event);
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(), event);
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;
}
}
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
* @param testContextName
* Name of the test context that should be used. If this value is null, the first
* test context found is used.
* @param useRandomRequestBodies
* defines is random request bodies are used or the body of the associated event
*/
public static Interaction createInteractionFromEventSequence(List sequence,
Model model,
String interactionName,
String testContextName,
boolean useRandomRequestBodies)
{
final Component testContext = fetchTestContext(model, testContextName);
if (testContext == null) {
throw new RuntimeException("Could not find any test context in the model");
}
final Operation operation = testContext.createOwnedOperation(interactionName, null, null);
operation.applyStereotype(UTPUtils.getTestCaseStereotype(model));
final Interaction interaction =
(Interaction) testContext.createPackagedElement(interactionName + "_Impl",
UMLPackage.Literals.INTERACTION);
operation.getMethods().add(interaction);
// create lifelines
Lifeline userLifeline = null;
for (Property property : fetchAllSUTProperties(testContext)) {
String serviceName = property.getName();
Lifeline targetLifeline = interaction.createLifeline(serviceName);
targetLifeline.setRepresents(property);
}
for (Property property : fetchAllTestComponentProperties(testContext)) {
// TODO check if this is still required
/*
* if (userLifeline != null) { throw new RuntimeException(
* "TestContext must only have one TestComponent for the application of usage-based testing."
* ); }
*/
userLifeline = interaction.createLifeline(property.getName());
userLifeline.setRepresents(property);
}
if (userLifeline == null) {
throw new RuntimeException("No TestComponent found, could not create user lifeline.");
}
if (interaction.getLifelines().size() < 2) {
throw new RuntimeException("Fewer than two lifelines created. No SUT found.");
}
int i = 0;
for (Event event : sequence) {
if (!(event.equals(Event.STARTEVENT) || event.equals(Event.ENDEVENT))) {
String serviceName = SOAPUtils.getServiceNameFromEvent(event);
String methodName = SOAPUtils.getCalledMethodFromEvent(event);
String clientName = SOAPUtils.getClientNameFromEvent(event);
String prefix = interactionName + "_" + i + "_" + methodName + "_";
// determine lifelines
Lifeline msgTargetLifeline;
Lifeline msgSourceLifeline;
msgSourceLifeline = interaction.getLifeline(clientName);
msgTargetLifeline = interaction.getLifeline(serviceName);
if (msgSourceLifeline == null) {
throw new RuntimeException(
"Error creating message: could not determine source lifeline for component: " +
clientName);
}
if (msgTargetLifeline == null) {
throw new RuntimeException(
"Error creating message: could not determine target lifeline for component: " +
serviceName);
}
// determine correct target interface
Set targetInterfaces =
getRealizedInterfacesFromProperty((Property) msgTargetLifeline.getRepresents());
if (targetInterfaces.isEmpty()) {
throw new RuntimeException("no interface associated with the property " +
msgTargetLifeline.getRepresents().getName());
}
Interface targetInterface = null;
for (Interface intface : targetInterfaces) {
if (getOperationFromName(intface.getOperations(), methodName) != null) {
// interface found
targetInterface = intface;
break;
}
}
if (targetInterface == null) {
StringBuilder errStrBuilder = new StringBuilder();
errStrBuilder
.append("Error creating message: operation not found in the implementing interfaces (");
Iterator iter = targetInterfaces.iterator();
while (iter.hasNext()) {
String interfaceName = iter.next().getName();
errStrBuilder.append(interfaceName);
if (iter.hasNext()) {
errStrBuilder.append(",");
}
else {
errStrBuilder.append("): " + methodName);
}
}
throw new RuntimeException(errStrBuilder.toString());
}
Operation calledOperation =
getOperationFromName(targetInterface.getOperations(), methodName);
// get connector
Connector connector = inferConnector(msgSourceLifeline, msgTargetLifeline);
boolean asynch = false;
if (calledOperation.getConcurrency() == CallConcurrencyKind.CONCURRENT_LITERAL) {
asynch = true;
}
if (SOAPUtils.isSOAPRequest(event)) {
// setup for both SYNCH and ASYNCH calls
MessageOccurrenceSpecification callSendFragment =
(MessageOccurrenceSpecification) interaction
.createFragment(prefix + "callSendFragment",
UMLPackage.Literals.MESSAGE_OCCURRENCE_SPECIFICATION);
MessageOccurrenceSpecification callRecvFragment =
(MessageOccurrenceSpecification) interaction
.createFragment(prefix + "callRecvFragment",
UMLPackage.Literals.MESSAGE_OCCURRENCE_SPECIFICATION);
callSendFragment.setCovered(msgSourceLifeline);
callRecvFragment.setCovered(msgTargetLifeline);
// create call
Message callMessage = interaction.createMessage(prefix + "call");
callMessage.setSignature(calledOperation);
setMessageParameters(callMessage, calledOperation, event,
useRandomRequestBodies, prefix);
callMessage.setConnector(connector);
callMessage.setSendEvent(callSendFragment);
callMessage.setReceiveEvent(callRecvFragment);
callSendFragment.setMessage(callMessage);
callRecvFragment.setMessage(callMessage);
if (asynch) {
// Create ASYNCH call
callMessage.setMessageSort(MessageSort.ASYNCH_CALL_LITERAL);
}
else {
// SYNCH call
callMessage.setMessageSort(MessageSort.SYNCH_CALL_LITERAL);
}
}
if (!asynch && SOAPUtils.isSOAPResponse(event)) {
// setup reply and behavior execution specifications
MessageOccurrenceSpecification replySendFragment =
(MessageOccurrenceSpecification) interaction
.createFragment(prefix + "replySendFragment",
UMLPackage.Literals.MESSAGE_OCCURRENCE_SPECIFICATION);
MessageOccurrenceSpecification replyRecvFragment =
(MessageOccurrenceSpecification) interaction
.createFragment(prefix + "replyRecvFragment",
UMLPackage.Literals.MESSAGE_OCCURRENCE_SPECIFICATION);
replySendFragment.setCovered(msgTargetLifeline);
replyRecvFragment.setCovered(msgSourceLifeline);
/*
* BehaviorExecutionSpecification sourceBehaviorExecutionSpecification =
* (BehaviorExecutionSpecification) interaction .createFragment(":" + methodName
* + "_sourceBhvExecSpec",
* UMLPackage.Literals.BEHAVIOR_EXECUTION_SPECIFICATION);
* BehaviorExecutionSpecification targetBehaviorExecutionSpecification =
* (BehaviorExecutionSpecification) interaction .createFragment(":" + methodName
* + "_targetBhvExecSpec",
* UMLPackage.Literals.BEHAVIOR_EXECUTION_SPECIFICATION);
*
* sourceBehaviorExecutionSpecification.setStart(callSendFragment);
* sourceBehaviorExecutionSpecification.setFinish(replyRecvFragment);
* targetBehaviorExecutionSpecification.setStart(callRecvFragment);
* targetBehaviorExecutionSpecification.setFinish(replySendFragment);
*/
// create reply
Message replyMessage = interaction.createMessage(prefix + "_reply");
replyMessage.setMessageSort(MessageSort.REPLY_LITERAL);
replyMessage.setSignature(calledOperation);
//setReplyMessageParameters(replyMessage, calledOperation);
setMessageParameters(replyMessage, calledOperation, event, useRandomRequestBodies, prefix);
replyMessage.setConnector(connector);
replyMessage.setSendEvent(replySendFragment);
replyMessage.setReceiveEvent(replyRecvFragment);
replySendFragment.setMessage(replyMessage);
replyRecvFragment.setMessage(replyMessage);
}
i++;
}
}
return interaction;
}
/**
*
* Calculates the usage score of an interaction as the logsum of the event probabilities
* multiplied with the length of the interaction.
*
*
* @param interaction
* interaction for which the score is calculated
* @param usageProfile
* usage profile used for the calculation
* @return calculated usage score
*/
public static double calculateUsageScore(Interaction interaction,
IStochasticProcess usageProfile)
{
double usageScore = 0.0d;
EList interactionFragments = interaction.getFragments();
List eventSequence = new LinkedList<>();
eventSequence.add(Event.STARTEVENT);
for (InteractionFragment interactionFragment : interactionFragments) {
if (interactionFragment instanceof MessageOccurrenceSpecification) {
Message message =
((MessageOccurrenceSpecification) interactionFragment).getMessage();
//if (message.getReceiveEvent().equals(interactionFragment) && isCallMessage(message))
if (message.getReceiveEvent().equals(interactionFragment))
{
String clientName;
String serviceName;
String methodName = message.getSignature().getName();
CallType callType;
if( isCallMessage(message) ) {
clientName =
((MessageOccurrenceSpecification) message.getSendEvent()).getCovereds()
.get(0).getName();
serviceName =
((MessageOccurrenceSpecification) message.getReceiveEvent()).getCovereds()
.get(0).getName();
callType = CallType.REQUEST;
} else {
clientName =
((MessageOccurrenceSpecification) message.getReceiveEvent()).getCovereds()
.get(0).getName();
serviceName =
((MessageOccurrenceSpecification) message.getSendEvent()).getCovereds()
.get(0).getName();
callType = CallType.RESPONSE;
}
eventSequence.add(new Event(new SimpleSOAPEventType(methodName, serviceName,
clientName, null, null, callType)));
}
}
}
eventSequence.add(Event.ENDEVENT);
double prob = usageProfile.getLogSum(eventSequence);
usageScore = eventSequence.size() * prob;
return usageScore;
}
/**
*
* Extends the given model with an activity for usage-based scheduling of the test cases.
*
*
* @param model
* model to be extended
* @param usageProfile
* usage profile used as foundation
*/
public static void createScheduling(Model model,
IStochasticProcess usageProfile,
String testContextName)
{
final Component testContext = fetchTestContext(model, testContextName);
if (testContext == null) {
throw new RuntimeException("Could not find any test context in the model");
}
Map usageScoreMapUnsorted = new HashMap<>();
// first, we determine all test cases and calculate their usage scores
final Stereotype utpTestCase = UTPUtils.getTestCaseStereotype(model);
for (Operation operation : testContext.getAllOperations()) {
if (operation.getAppliedStereotypes().contains(utpTestCase) &&
!operation.getMethods().isEmpty())
{
Interaction interaction = (Interaction) operation.getMethods().get(0);
usageScoreMapUnsorted
.put(operation, calculateUsageScore(interaction, usageProfile));
}
}
Map usageScoreMapSorted = sortByValue(usageScoreMapUnsorted);
// now we create the scheduling
Activity schedulingActivity =
(Activity) testContext.createOwnedBehavior("UsageBasedScheduling",
UMLPackage.Literals.ACTIVITY);
testContext.setClassifierBehavior(schedulingActivity);
ActivityNode startNode =
schedulingActivity.createOwnedNode("final", UMLPackage.Literals.INITIAL_NODE);
ActivityNode finalNode =
schedulingActivity.createOwnedNode("final", UMLPackage.Literals.ACTIVITY_FINAL_NODE);
ActivityNode currentOperationNode = startNode;
for (Entry entry : usageScoreMapSorted.entrySet()) {
Operation operation = entry.getKey();
CallOperationAction nextOperationNode =
(CallOperationAction) schedulingActivity
.createOwnedNode(operation.getName(), UMLPackage.Literals.CALL_OPERATION_ACTION);
nextOperationNode.setOperation(operation);
ActivityEdge edge =
schedulingActivity.createEdge(currentOperationNode.getName() + "_to_" +
nextOperationNode.getName(), UMLPackage.Literals.CONTROL_FLOW);
edge.setSource(currentOperationNode);
edge.setTarget(nextOperationNode);
currentOperationNode = nextOperationNode;
}
ActivityEdge edge =
schedulingActivity
.createEdge(currentOperationNode.getName() + "_to_" + finalNode.getName(),
UMLPackage.Literals.CONTROL_FLOW);
edge.setSource(currentOperationNode);
edge.setTarget(finalNode);
}
/**
*
* 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, Event event) {
String eventService = SOAPUtils.getServiceNameFromEvent(event);
String eventMethod = SOAPUtils.getCalledMethodFromEvent(event);
Map interfaceServiceMap =
createInterfaceServiceMap(transitions.get(0).getModel());
List matching = new LinkedList<>();
for (Transition transition : transitions) {
EList triggers = transition.getTriggers();
if (triggers.size() == 1) {
if (triggers.get(0).getEvent() instanceof CallEvent) {
CallEvent callEvent = (CallEvent) triggers.get(0).getEvent();
String transitionMethod = callEvent.getOperation().getName();
String transitionService =
interfaceServiceMap.get(callEvent.getOperation().getInterface());
if (eventMethod.equals(transitionMethod) &&
eventService.equals(transitionService))
{
matching.add(transition);
}
}
}
else {
throw new RuntimeException(
"only one trigger of type CallEvent per transition allowed: " +
transition.getName());
}
}
return matching;
}
/**
*
* Fetches all realized interfaces from the type of a UML {@link Property} (i.e.,
* property.getType()). If no interfaces are realized, an empty set is returned.
*
*
* @param property
* property, of the whose realized interfaces of the type are determined
* @return realized interfaces
*/
private static Set getRealizedInterfacesFromProperty(Property property) {
return getRealizedInterfaceFromComponent((Component) property.getType());
}
/**
*
* Fetches all realized interfaces from a UML {@link Component}. If no interfaces are realized,
* an empty set is returned.
*
*
* @param comp
* component whose realized interfaces are determined
* @return realized interfaces
*/
private static Set getRealizedInterfaceFromComponent(Component component) {
Set interfaces = new HashSet<>();
// Interface myInterface = null;
for (Property property : component.getAllAttributes()) {
if (property instanceof Port) {
Port port = (Port) property;
if (!port.isConjugated()) {
interfaces.addAll(port.getProvideds());
}
}
}
return interfaces;
}
/**
*
* Determines searches within a {@link Package} for a component to which the UTP TestContext
* stereotype is applied.
*
* - If no testContextName is provided, the first test context found is returned.
* - In case no test context is found, null is returned.
*
*
* @param pkg
* package where the test context is being locked for
* @param testContextName
* name of the test context; in case no test name is specified, use null and not the
* empty String.
* @return {@link Component} to which the TestContext stereotype is applied
*/
private static Component fetchTestContext(final Package pkg, final String testContextName) {
List testContexts = fetchAllTestContexts(pkg);
if (testContexts.isEmpty()) {
return null;
}
if (testContextName != null) {
for (Component testContext : testContexts) {
if (testContextName.equals(testContext.getName())) {
return testContext;
}
}
return null;
}
else {
return testContexts.get(0);
}
}
/**
*
* Retrieves all UML {@link Component}s to which the UTP TestContext stereotype is applied from
* a package. This method calls itself recursively to include all components contained in
* sub-packages.
*
*
* In case no test context is found, an empty list is returned.
*
*
* @param pkg
* package from which the test contexts are retrieved
* @return {@link List} of test contexts
*/
private static List fetchAllTestContexts(final Package pkg) {
final Stereotype utpTestContext = UTPUtils.getTestContextStereotype(pkg.getModel());
final List testContexts = new LinkedList<>();
for (Element element : pkg.getOwnedElements()) {
if (element instanceof Package) {
testContexts.addAll(fetchAllTestContexts((Package) element));
}
if (element instanceof Component &&
element.getAppliedStereotypes().contains(utpTestContext))
{
testContexts.add((Component) element);
}
}
return testContexts;
}
/**
*
* Retrieves all properties that represent a UTP TestComponent from a test context.
*
*
* @param testContext
* test context from which the properties are retrieved
* @return properties that represent test components
*/
private static Set fetchAllTestComponentProperties(final Component testContext) {
// fetch all SUTs and TestComponents
final Stereotype utpTestComponent =
UTPUtils.getTestComponentStereotype(testContext.getModel());
final Set properties = new HashSet<>();
for (Property property : testContext.getAllAttributes()) {
if (property.getType().getAppliedStereotypes().contains(utpTestComponent)) {
properties.add(property);
}
}
return properties;
}
/**
*
* Retrieves all properties that represent a UTP SUT from a test context.
*
*
* @param testContext
* test context from which the properties are retrieved
* @return properties that represent the SUTs
*/
private static Set fetchAllSUTProperties(final Component testContext) {
// fetch all SUTs and TestComponents
final Stereotype utpSUT = UTPUtils.getSUTStereotype(testContext.getModel());
final Set properties = new HashSet<>();
for (Property property : testContext.getAllAttributes()) {
if (property.getAppliedStereotypes().contains(utpSUT)) {
properties.add(property);
}
}
return properties;
}
/**
*
* Infers connector between two lifelines.
*
*
* TODO: Currently assumes only one connector between two lifelines possible. This assumption is
* invalid as soon as there are two ports that connect the same two properties.
*
*
* @param msgSourceLifeline
* source lifeline of the message
* @param targetAttributes
* target lifeline of the message
*/
private static Connector inferConnector(Lifeline msgSourceLifeline, Lifeline msgTargetLifeline)
{
EList userAttributes =
((Component) msgSourceLifeline.getRepresents().getType()).getAttributes();
EList targetAttributes =
((Component) msgTargetLifeline.getRepresents().getType()).getAttributes();
for (Property userAttribute : userAttributes) {
if (userAttribute instanceof Port) {
EList userEnds = ((Port) userAttribute).getEnds();
for (ConnectorEnd userEnd : userEnds) {
Connector userConnector = (Connector) userEnd.eContainer();
for (Property targetAttribute : targetAttributes) {
if (targetAttribute instanceof Port) {
EList targetEnds = ((Port) targetAttribute).getEnds();
for (ConnectorEnd targetEnd : targetEnds) {
Connector targetConnector = (Connector) targetEnd.eContainer();
if (targetConnector == userConnector) {
return targetConnector;
}
}
}
}
}
}
}
return null;
}
/**
*
* Creates a map that maps the interfaces to the properties, i.e., services that they are
* represented by.
*
*
* TODO: currently assumes that each interfaces is only realized by one property
*
*
* @param model
* model for which the interface->service map is created
* @return the map
*/
private static Map createInterfaceServiceMap(Model model) {
Map interfaceServiceMap = new HashMap<>();
List testContexts = fetchAllTestContexts(model.getModel());
for (Component testContext : testContexts) {
for (Property property : fetchAllSUTProperties(testContext)) {
for (Interface intface : getRealizedInterfacesFromProperty(property)) {
interfaceServiceMap.put(intface, property.getName());
}
}
for (Property property : fetchAllTestComponentProperties(testContext)) {
for (Interface intface : getRealizedInterfacesFromProperty(property)) {
interfaceServiceMap.put(intface, property.getName());
}
}
}
return interfaceServiceMap;
}
/**
*
* Sets values for the parameters of a call message. The values are, if possible, inferred from
* the event that is provided.
*
*
* @param message
* call message for which the parameters are set
* @param calledOperation
* operation that is called by the message
* @param event
* event that provides the parameters; in case of null, default values are assumed
* @param useRandomMsgBodies
* defines is random request bodies are used or the body of the associated event
* @param prefix
* prefix of the call message; used to create good warnings and debugging information
*/
private static void setMessageParameters(Message message,
Operation calledOperation,
Event event,
boolean useRandomMsgBodies,
String prefix)
{
org.w3c.dom.Element requestBody;
if(SOAPUtils.isSOAPRequest(event)) {
requestBody =
SOAPUtils.getSoapBodyFromEvent(event, useRandomMsgBodies, CallType.REQUEST);
} else {
requestBody =
SOAPUtils.getSoapBodyFromEvent(event, useRandomMsgBodies, CallType.RESPONSE);
}
Package instSpecPkg = null;
// FOR DEBUGGING
// Console.traceln(Level.FINE, "XML structure of path:" + StringTools.ENDLINE +
// SOAPUtils.getSerialization(requestBody));
// for( Parameter param : calledOperation.getOwnedParameters() ) {
// System.out.println(param.getName());
// if( param.getType() instanceof DataType ) {
// for( Property prop1 : ((DataType) param.getType()).getAllAttributes() ) {
// System.out.println(" " + prop1.getName());
// if( prop1.getType() instanceof DataType ) {
// for( Property prop2 : ((DataType) prop1.getType()).getAllAttributes() ) {
// System.out.println(" " + prop2.getName());
// if( prop2.getType() instanceof DataType ) {
// for( Property prop3 : ((DataType) prop2.getType()).getAllAttributes() ) {
// System.out.println(" " + prop3.getName());
// if( prop3.getType() instanceof DataType ) {
// for( Property prop4 : ((DataType) prop3.getType()).getAllAttributes() ) {
// System.out.println(" " + prop4.getName());
// }
// }
// }
// }
// }
// }
// }
// }
// }
// Set parameters of operation
for (Parameter param : calledOperation.getOwnedParameters()) {
if (instSpecPkg == null) {
instSpecPkg =
getOrCreateInstanceSpecificationPackage(message.getModel(), event);
}
// TODO String path = calledOperation.getName() + ":" + param.getName();
String path = calledOperation.getName() + ":" + param.getType().getName();
// create param node
// Expression argument =
// (Expression) callMessage.createArgument(param.getName(), param.getType(),
// UMLPackage.Literals.EXPRESSION);
if ( (isInParameter(param) && SOAPUtils.isSOAPRequest(event)) ||
(isOutParameter(param) && SOAPUtils.isSOAPResponse(event))) {
// create parameters node
if (!(param.getType() instanceof DataType)) {
throw new RuntimeException("TODO error handling; parameters missing");
}
DataType parametersNode = (DataType) param.getType();
InstanceSpecification instSpecParameters =
(InstanceSpecification) instSpecPkg.createPackagedElement(prefix + "instspec_" +
param.getType().getName(), UMLPackage.Literals.INSTANCE_SPECIFICATION);
instSpecParameters.getClassifiers().add((DataType) param.getType());
// InstanceValue parametersValue =
// (InstanceValue) argument
// .createOperand(param.getType().getName(), param.getType(),
// UMLPackage.Literals.INSTANCE_VALUE);
// parametersValue.setInstance(instSpecParameters);
InstanceValue instanceValue =
(InstanceValue) message.createArgument(param.getName(), param.getType(),
UMLPackage.Literals.INSTANCE_VALUE);
instanceValue.setInstance(instSpecParameters);
for (Property internalParameter : parametersNode.getAllAttributes()) {
if (internalParameter.getType() instanceof DataType) {
List paramNodes =
SOAPUtils.getMatchingChildNode(internalParameter.getType().getName(),
requestBody);
// TODO the mistake is somewhere around here ... probably
// List paramNodes =
// SOAPUtils.getMatchingChildNode(param.getName(), requestBody);
int multiplicityChosen = paramNodes.size();
if (multiplicityChosen == 0 && internalParameter.getLower() > 0) {
Console
.traceln(Level.WARNING,
"required attribute not found in SOAP message: " + path);
Console
.traceln(Level.WARNING,
"setting default values for this attribute and all its children");
Console.traceln(Level.FINE, "XML structure of path:" +
StringTools.ENDLINE + SOAPUtils.getSerialization(requestBody));
multiplicityChosen = internalParameter.getLower();
}
for (int i = 0; i < multiplicityChosen; i++) {
org.w3c.dom.Element paramNode = null;
if (!paramNodes.isEmpty()) {
paramNode = paramNodes.get(i);
}
Slot slot = instSpecParameters.createSlot();
slot.setDefiningFeature(internalParameter);
InstanceValue value =
(InstanceValue) slot
.createValue(internalParameter.getName() + "_" + i,
internalParameter.getType(),
UMLPackage.Literals.INSTANCE_VALUE);
value
.setInstance(createInstanceSpecification((DataType) internalParameter
.getType(),
instSpecPkg, prefix,
paramNode, path));
/*
* InstanceValue value = (InstanceValue) argument .createOperand(null,
* internalParameter.getType(), UMLPackage.Literals.INSTANCE_VALUE);
* value.setInstance(instSpec);
*/
}
}
else if (internalParameter.getType() instanceof PrimitiveType) {
createSlotPrimitiveType(instSpecParameters, internalParameter, requestBody,
path);
}
}
}
else {
// set literalNull for out and return parameters
// argument.createOperand(null, param.getType(), UMLPackage.Literals.LITERAL_NULL);
message.createArgument(param.getName(), param.getType(),
UMLPackage.Literals.LITERAL_NULL);
}
}
}
/**
*
* Creates an {@link InstanceSpecification} for a data type in the given package. The values are
* inferred, if possible, from the DOM node. The prefix and the path are used for naming the
* instance specification and to provide good warnings and debug information in case of
* problems.
*
*
* @param type
* DataType for which the {@link InstanceSpecification} is created
* @param pkg
* package in which the {@link InstanceSpecification} is created
* @param prefix
* prefix used for naming the {@link InstanceSpecification}
* @param currentNode
* node of a DOM from which values are inferred
* @param path
* used for warnings and debug information
* @return {@link InstanceSpecification} for the given type
*/
private static InstanceSpecification createInstanceSpecification(DataType type,
Package pkg,
String prefix,
org.w3c.dom.Element currentNode,
String path)
{
if ("".equals(path)) {
path = type.getName();
}
InstanceSpecification instSpec =
(InstanceSpecification) pkg
.createPackagedElement(prefix + "instspec_" + type.getName(),
UMLPackage.Literals.INSTANCE_SPECIFICATION);
instSpec.getClassifiers().add(type);
for (Property prop : type.getAllAttributes()) {
if (prop.getType() instanceof PrimitiveType) {
createSlotPrimitiveType(instSpec, prop, currentNode, path);
}
else if (prop.getType() instanceof DataType) {
List attributeNodes = null;
int multiplicityChosen = 0;
if (currentNode != null) {
// TODO attributeNodes = SOAPUtils.getMatchingChildNode(prop.getName(),
// currentNode);
attributeNodes = SOAPUtils.getMatchingChildNode(prop.getName(), currentNode);
multiplicityChosen = attributeNodes.size();
}
if (multiplicityChosen == 0 && prop.getLower() > 0) {
if (currentNode != null) {
Console.traceln(Level.WARNING,
"required attribute not found in SOAP message: " + path +
"." + prop.getName());
Console
.traceln(Level.WARNING,
"setting default values for this attribute and all its children");
Console.traceln(Level.FINE, "XML structure of path:" + StringTools.ENDLINE +
SOAPUtils.getSerialization(currentNode));
}
multiplicityChosen = prop.getLower();
}
for (int i = 0; i < multiplicityChosen; i++) {
org.w3c.dom.Element attributeNode = null;
if (attributeNodes != null && !attributeNodes.isEmpty()) {
attributeNode = attributeNodes.get(i);
}
Slot slot = instSpec.createSlot();
slot.setDefiningFeature(prop);
InstanceValue value =
(InstanceValue) slot.createValue(prop.getName() + "_" + i, prop.getType(),
UMLPackage.Literals.INSTANCE_VALUE);
value.setInstance(createInstanceSpecification((DataType) prop.getType(), pkg,
prefix, attributeNode, path +
"." + prop.getName()));
}
}
else {
Console.traceln(Level.SEVERE, "property neither DataType nor PrimitiveType: " +
prop.getType());
// TODO abort?
}
}
return instSpec;
}
/**
*
* Gets or creates a {@link Package} for {@link InstanceSpecification} created by the
* usage-based testing. Each service gets its own sub-package within a package called
* UBT_InstanceSpecifications. "
*
*
* @param model
* model in which the package is generated
* @param event
* event from which the service name is inferred
* @return package for the {@link InstanceSpecification}s
*/
private static Package getOrCreateInstanceSpecificationPackage(Model model, Event event) {
String pkgUBTInstSpecs = "UBT_InstanceSpecifications";
Package ubtInstSpecPkg = (Package) model.getOwnedMember(pkgUBTInstSpecs);
if (ubtInstSpecPkg == null) {
ubtInstSpecPkg =
(Package) model.createPackagedElement(pkgUBTInstSpecs, UMLPackage.Literals.PACKAGE);
}
String serviceName = SOAPUtils.getServiceNameFromEvent(event);
Package serviceInstSpecPkg = (Package) ubtInstSpecPkg.getOwnedMember(serviceName);
if (serviceInstSpecPkg == null) {
serviceInstSpecPkg =
(Package) ubtInstSpecPkg.createPackagedElement(serviceName,
UMLPackage.Literals.PACKAGE);
}
return serviceInstSpecPkg;
}
/**
*
* Creates an operand that defines a {@link PrimitiveType}.
*
*
* TODO: Currently does nothing in case of multiplicity 0. I am not sure if, in that case, one
* has to define LiteralNull instead.
*
*
* @param param
* parameter for which the operand is created
* @param argument
* argument to which the operand is added
* @param currentNode
* DOM node from which is value for the operand is inferred
* @param path
* used for warnings and debug information
*/
private static void createOperandPrimitiveType(Parameter param,
Expression argument,
org.w3c.dom.Element currentNode,
String path)
{
List attributeValues = SOAPUtils.getValuesFromElement(param.getName(), currentNode);
if (attributeValues.isEmpty()) {
if (param.getLower() == 0) {
// ignoring optional attribute
return;
}
else {
if (currentNode != null) {
Console.traceln(Level.WARNING,
"required attribute not found in SOAP message: " + path + "." +
param.getName());
Console.traceln(Level.WARNING, "setting default values for this attribute");
Console.traceln(Level.FINE, "XML structure of path:" + StringTools.ENDLINE +
SOAPUtils.getSerialization(currentNode));
}
attributeValues.add(null);
}
}
for (String attributeValue : attributeValues) {
if ("String".equals(param.getType().getName())) {
LiteralString spec =
(LiteralString) argument.createOperand(param.getName(), null,
UMLPackage.Literals.LITERAL_STRING);
if (attributeValue != null) {
spec.setValue(attributeValue);
}
else {
spec.setValue("foobar");
}
}
else if ("Integer".equals(param.getType().getName())) {
LiteralInteger spec =
(LiteralInteger) argument.createOperand(param.getName(), null,
UMLPackage.Literals.LITERAL_INTEGER);
if (attributeValue != null) {
spec.setValue(Integer.parseInt(attributeValue));
}
else {
spec.setValue(42);
}
}
else if ("Boolean".equals(param.getType().getName())) {
LiteralBoolean spec =
(LiteralBoolean) argument.createOperand(param.getName(), null,
UMLPackage.Literals.LITERAL_BOOLEAN);
if (attributeValue != null) {
spec.setValue(Boolean.parseBoolean(attributeValue));
}
else {
spec.setValue(true);
}
}
else if ("Real".equals(param.getType().getName())) {
LiteralReal spec =
(LiteralReal) argument.createOperand(param.getName(), null,
UMLPackage.Literals.LITERAL_REAL);
if (attributeValue != null) {
spec.setValue(Double.parseDouble(attributeValue));
}
else {
spec.setValue(3.14);
}
}
}
}
/**
*
* Creates a {@link Slot} in an {@link InstanceSpecification} for a primitive type.
*
*
* @param instSpec
* instance specification to which the slot is added
* @param prop
* property that describes the slot
* @param currentNode
* DOM node from which is value for the slot is inferred
* @param path
* used for warnings and debug information
*/
private static void createSlotPrimitiveType(InstanceSpecification instSpec,
Property prop,
org.w3c.dom.Element currentNode,
String path)
{
List attributeValues = SOAPUtils.getValuesFromElement(prop.getName(), currentNode);
if (attributeValues.isEmpty()) {
if (prop.getLower() == 0) {
// ignoring optional attribute
return;
}
else {
if (currentNode != null) {
Console.traceln(Level.WARNING,
"required attribute not found in SOAP message: " + path + "." +
prop.getName());
Console.traceln(Level.WARNING, "setting default values for this attribute");
Console.traceln(Level.FINE, "XML structure of path:" + StringTools.ENDLINE +
SOAPUtils.getSerialization(currentNode));
}
attributeValues.add(null);
}
}
for (String attributeValue : attributeValues) {
Slot slot = instSpec.createSlot();
slot.setDefiningFeature(prop);
if ("String".equals(prop.getType().getName())) {
LiteralString value =
(LiteralString) slot.createValue(prop.getName(), null,
UMLPackage.Literals.LITERAL_STRING);
if (attributeValue != null) {
value.setValue(attributeValue);
}
else {
value.setValue("foobar");
}
}
else if ("Integer".equals(prop.getType().getName())) {
LiteralInteger value =
(LiteralInteger) slot.createValue(prop.getName(), null,
UMLPackage.Literals.LITERAL_INTEGER);
if (attributeValue != null) {
value.setValue(Integer.parseInt(attributeValue));
}
else {
value.setValue(42);
}
}
else if ("Boolean".equals(prop.getType().getName())) {
LiteralBoolean value =
(LiteralBoolean) slot.createValue(prop.getName(), null,
UMLPackage.Literals.LITERAL_BOOLEAN);
if (attributeValue != null) {
value.setValue(Boolean.parseBoolean(attributeValue));
}
else {
value.setValue(true);
}
}
else if ("Real".equals(prop.getType().getName())) {
LiteralReal value =
(LiteralReal) slot.createValue(prop.getName(), null,
UMLPackage.Literals.LITERAL_REAL);
if (attributeValue != null) {
value.setValue(Double.parseDouble(attributeValue));
}
else {
value.setValue(3.14);
}
}
else {
Console.traceln(Level.SEVERE, "could not create literal for primitive type: " +
prop.getType().getName());
// TODO abort?
}
}
}
/**
*
* Sets values for the parameters of a reply message. The values are, all LiterealNull and to
* the INOUT, OUT and REPLY parameters, the UTP stereotype LiteralAny is applied.
*
*
* @param replyMessage
* reply message for which the parameters are set
* @param calledOperation
* operation that is replied for by the message
*/
private static void setReplyMessageParameters(Message replyMessage, Operation calledOperation) {
for (Parameter param : calledOperation.getOwnedParameters()) {
LiteralNull argument =
(LiteralNull) replyMessage.createArgument(param.getName(), param.getType(),
UMLPackage.Literals.LITERAL_NULL);
if (isOutParameter(param)) {
argument.applyStereotype(UTPUtils.getLiteralAnyStereotype(replyMessage.getModel()));
}
}
}
/**
*
* Checks if a parameter has the direction IN or INOUT
*
*
* @param parameter
* parameter that is checked
* @return true if the direction is IN or INOUT; false otherwise
*/
private static boolean isInParameter(Parameter parameter) {
return parameter.getDirection() == ParameterDirectionKind.IN_LITERAL ||
parameter.getDirection() == ParameterDirectionKind.INOUT_LITERAL;
}
/**
*
* Checks if a parameter has the direction RETURN, OUT or INOUT
*
*
* @param parameter
* parameter that is checked
* @return true if the direction is RETURN, OUT, or INOUT; false otherwise
*/
private static boolean isOutParameter(Parameter parameter) {
return parameter.getDirection() == ParameterDirectionKind.RETURN_LITERAL ||
parameter.getDirection() == ParameterDirectionKind.OUT_LITERAL ||
parameter.getDirection() == ParameterDirectionKind.INOUT_LITERAL;
}
/**
*
* Checks if the {@link MessageSort} of a message is a call message, i.e., ASYNCH_CALL or
* SYNCH_CALL.
*
*
* @param message
* message that is checked
* @return true if the message is a call message; false otherwise
*/
private static boolean isCallMessage(Message message) {
if (message == null) {
return false;
}
MessageSort msgSort = message.getMessageSort();
return msgSort == MessageSort.ASYNCH_CALL_LITERAL ||
msgSort == MessageSort.SYNCH_CALL_LITERAL;
}
/**
*
* inverse-sorts the values of a map. Has been adapted from this StackOverflow post.
*
*
* @param map
* map whose values are sorted
* @return sorted version of the map
*/
private static > Map sortByValue(Map map) {
// TODO possibly move to another class
List> list = new LinkedList<>(map.entrySet());
Collections.sort(list, new Comparator>() {
@Override
public int compare(Map.Entry o1, Map.Entry o2) {
return -1 * (o1.getValue()).compareTo(o2.getValue());
}
});
Map result = new LinkedHashMap<>();
for (Map.Entry entry : list) {
result.put(entry.getKey(), entry.getValue());
}
return result;
}
}