source: trunk/autoquest-plugin-uml/src/main/java/de/ugoe/cs/autoquest/plugin/uml/UMLUtils.java @ 1900

Last change on this file since 1900 was 1900, checked in by sherbold, 9 years ago
  • updated model validation to report violation counts
  • code documentation
  • Property svn:mime-type set to text/plain
File size: 42.9 KB
Line 
1//   Copyright 2012 Georg-August-Universität Göttingen, Germany
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
15package de.ugoe.cs.autoquest.plugin.uml;
16
17import java.util.Collection;
18import java.util.Collections;
19import java.util.Comparator;
20import java.util.HashMap;
21import java.util.HashSet;
22import java.util.Iterator;
23import java.util.LinkedHashMap;
24import java.util.LinkedList;
25import java.util.List;
26import java.util.Map;
27import java.util.Map.Entry;
28import java.util.logging.Level;
29import java.util.Set;
30import java.util.TreeSet;
31
32import org.eclipse.emf.common.util.EList;
33import org.eclipse.uml2.uml.Activity;
34import org.eclipse.uml2.uml.ActivityEdge;
35import org.eclipse.uml2.uml.ActivityNode;
36import org.eclipse.uml2.uml.CallConcurrencyKind;
37import org.eclipse.uml2.uml.CallOperationAction;
38import org.eclipse.uml2.uml.Comment;
39import org.eclipse.uml2.uml.Component;
40import org.eclipse.uml2.uml.Connector;
41import org.eclipse.uml2.uml.ConnectorEnd;
42import org.eclipse.uml2.uml.Element;
43import org.eclipse.uml2.uml.Interaction;
44import org.eclipse.uml2.uml.InteractionFragment;
45import org.eclipse.uml2.uml.Interface;
46import org.eclipse.uml2.uml.Lifeline;
47import org.eclipse.uml2.uml.Message;
48import org.eclipse.uml2.uml.MessageOccurrenceSpecification;
49import org.eclipse.uml2.uml.MessageSort;
50import org.eclipse.uml2.uml.Model;
51import org.eclipse.uml2.uml.Operation;
52import org.eclipse.uml2.uml.Package;
53import org.eclipse.uml2.uml.Port;
54import org.eclipse.uml2.uml.Profile;
55import org.eclipse.uml2.uml.Property;
56import org.eclipse.uml2.uml.Region;
57import org.eclipse.uml2.uml.StateMachine;
58import org.eclipse.uml2.uml.Stereotype;
59import org.eclipse.uml2.uml.Transition;
60import org.eclipse.uml2.uml.UMLPackage;
61import org.eclipse.uml2.uml.Vertex;
62
63import de.ugoe.cs.autoquest.eventcore.Event;
64import de.ugoe.cs.autoquest.plugin.http.eventcore.SOAPEventType;
65import de.ugoe.cs.autoquest.plugin.http.eventcore.SimpleSOAPEventType;
66import de.ugoe.cs.autoquest.plugin.uml.eventcore.UMLTransitionType;
67import de.ugoe.cs.autoquest.usageprofiles.IStochasticProcess;
68import de.ugoe.cs.util.console.Console;
69
70/**
71 * <p>
72 * Utilities for working with UML.
73 * </p>
74 *
75 * @author Steffen Herbold
76 */
77public class UMLUtils {
78
79    /**
80     * <p>
81     * Method for checking if the information in a usage journal can be mapped to the SUT model. In
82     * case this is not possible, the violations are reported.
83     * </p>
84     *
85     * @param sequences
86     *            sequences of the usage journal
87     * @param model
88     *            SUT model that is validated
89     * @param testContextName
90     *            name of the test context to be used; if null, the first test context found is used
91     * @return number of violations
92     */
93    public static int validateModelWithLog(Collection<List<Event>> sequences,
94                                           Model model,
95                                           String testContextName)
96    {
97        final Profile utpProfile = model.getAppliedProfile("utp");
98        final Stereotype utpTestComponent = (Stereotype) utpProfile.getOwnedMember("TestComponent");
99        final Stereotype utpSUT = (Stereotype) utpProfile.getOwnedMember("SUT");
100        final Stereotype utpTestContext = (Stereotype) utpProfile.getOwnedMember("TestContext");
101
102        int violationCount = 0;
103        Component testContext = fetchTestContext(model, utpTestContext, testContextName);
104        if (testContext == null) {
105            violationCount++;
106            if (testContextName == null) {
107                Console.traceln(Level.SEVERE, "Could not find any TestContext in the model.");
108
109            }
110            else {
111                Console.traceln(Level.SEVERE, "Could not find TestContext in the model: " +
112                    testContextName);
113            }
114            Console
115                .traceln(Level.SEVERE,
116                         "Hint: Check if you have applied the TestContext stereotype correctly in the model.");
117            Console.traceln(Level.SEVERE, "Aborting");
118            return violationCount;
119        }
120
121        // Create list of unique methods calls
122        HashMap<String, Set<String>> calledMethods = new HashMap<>();
123        for (List<Event> sequence : sequences) {
124            for (Event event : sequence) {
125                if (event.getType() instanceof SOAPEventType) {
126                    SOAPEventType eventType = (SOAPEventType) event.getType();
127                    Set<String> curCalledMethods = calledMethods.get(eventType.getServiceName());
128                    if (curCalledMethods == null) {
129                        curCalledMethods = new TreeSet<>();
130                        calledMethods.put(eventType.getServiceName(), curCalledMethods);
131                    }
132                    curCalledMethods.add(eventType.getCalledMethod());
133                }
134            }
135        }
136
137        Console.traceln(Level.INFO,
138                        "Found the following services and operations in the usage data: ");
139        for (Entry<String, Set<String>> entry : calledMethods.entrySet()) {
140            Console.trace(Level.INFO, "\tService \"" + entry.getKey() + "\": ");
141            Iterator<String> iter = entry.getValue().iterator();
142            StringBuilder strBld = new StringBuilder();
143            while (iter.hasNext()) {
144                strBld.append(iter.next());
145                if (iter.hasNext()) {
146                    strBld.append(", ");
147                }
148            }
149            Console.traceln(Level.INFO, strBld.toString());
150        }
151
152        // fetch all SUTs and TestComponents
153        HashMap<String, Property> properties = new HashMap<>();
154        for (Property property : testContext.getAllAttributes()) {
155            if (property.getAppliedStereotypes().contains(utpSUT)) {
156                properties.put(property.getName(), property);
157            }
158            else if (property.getType().getAppliedStereotypes().contains(utpTestComponent)) {
159                properties.put(property.getName(), property);
160            }
161        }
162        Console.traceln(Level.INFO, "Found the following services in the TestConfiguration:");
163        for (Entry<String, Property> entry : properties.entrySet()) {
164            Console.traceln(Level.INFO, "\t" + entry.getKey());
165        }
166
167        for (Entry<String, Set<String>> entry : calledMethods.entrySet()) {
168            String serviceName = entry.getKey();
169            Console.traceln(Level.INFO, "Checking service: " + serviceName);
170            Set<String> methodNames = entry.getValue();
171            Property property = properties.get(serviceName);
172            if (property == null) {
173                violationCount++;
174                Console.traceln(Level.SEVERE, "\tCould not find property for service: " +
175                    serviceName);
176                Console
177                    .traceln(Level.SEVERE,
178                             "\tHint: Check service name map and/or model if the service is present and spelled correctly.");
179                Console
180                    .traceln(Level.SEVERE,
181                             "\tHint: Check if the SUT/TestComponent stereotype has been applied correctly in this TestContext.");
182            }
183            else {
184                Set<Interface> interfaces = getRealizedInterfacesFromProperty(property);
185                if (interfaces.isEmpty()) {
186                    violationCount++;
187                    Console
188                        .traceln(Level.SEVERE,
189                                 "\tCould not find any interfaces implementing the property for service: " +
190                                     serviceName);
191                    Console
192                        .traceln(Level.SEVERE,
193                                 "\tHint: Check if the property correctly realizes the interfaces in the model.");
194                }
195                else {
196                    Console.traceln(Level.INFO,
197                                    "\tFound the following realized interfaces for the service \"" +
198                                        serviceName + "\": ");
199                    Iterator<Interface> iter = interfaces.iterator();
200                    while (iter.hasNext()) {
201                        String interfaceName = iter.next().getName();
202                        StringBuilder strBld = new StringBuilder();
203                        strBld.append(interfaceName);
204                        if (iter.hasNext()) {
205                            strBld.append(", ");
206                        }
207                        Console.traceln(Level.INFO, strBld.toString());
208                    }
209                    for (String methodName : methodNames) {
210                        boolean methodFound = false;
211                        for (Interface intface : interfaces) {
212                            if (getOperationFromName(intface.getOperations(), methodName) != null) {
213                                // interface found
214                                Console.traceln(Level.INFO, "\tMethod " + methodName +
215                                    " found in interface " + intface.getName());
216                                methodFound = true;
217                            }
218                        }
219                        if (!methodFound) {
220                            violationCount++;
221                            Console.traceln(Level.SEVERE, "\tCould not find operation: " +
222                                methodName);
223                            Console
224                                .traceln(Level.SEVERE,
225                                         "\tHint: check if operation is present and spelled correctly");
226                        }
227                    }
228                }
229            }
230        }
231        return violationCount;
232    }
233
234    /**
235     * <p>
236     * Creates a sequence of events with {@link UMLTransitionType} as event type from a given
237     * sequence of events with the {@link SOAPEventType}, by matching the sequences to a state
238     * machine.
239     * </p>
240     *
241     * @param sequence
242     *            SOAP sequences
243     * @param stateMachine
244     *            the state machine
245     * @return create UML sequences
246     */
247    public static List<Event> createUMLTransitionSequence(List<Event> sequence,
248                                                          StateMachine stateMachine)
249    {
250        List<List<Transition>> matchingSequences =
251            determineMatchingTransitionSequences(sequence, stateMachine);
252
253        if (matchingSequences.size() != 1) {
254            throw new RuntimeException("no unique match found; " + matchingSequences.size() +
255                " matches");
256        }
257        List<Event> umlEventSequence = new LinkedList<>();
258        for (Transition transition : matchingSequences.get(0)) {
259            umlEventSequence.add(new Event(new UMLTransitionType(transition)));
260        }
261        return umlEventSequence;
262    }
263
264    /**
265     * <p>
266     * Uses a sequences of events with the {@link UMLTransitionType} to determine the transition
267     * probabilities for the state machine.
268     * </p>
269     *
270     * @param sequences
271     *            UML sequences
272     * @param stateMachine
273     *            state machine to be converted to a usage profile
274     */
275    public static void convertStateMachineToUsageProfile(Collection<List<Event>> sequences,
276                                                         StateMachine stateMachine)
277    {
278        // create state->outgoings hashmap
279        Map<Vertex, Map<Transition, Integer>> stateMap = new HashMap<>();
280        for (Region region : stateMachine.getRegions()) {
281            for (Vertex state : region.getSubvertices()) {
282                stateMap.put(state, new HashMap<Transition, Integer>());
283            }
284        }
285
286        // create counters for each transition
287        for (List<Event> sequence : sequences) {
288            for (Event event : sequence) {
289                if (event.getType() instanceof UMLTransitionType) {
290                    Transition transition = ((UMLTransitionType) event.getType()).getTransition();
291                    Map<Transition, Integer> transitionMap = stateMap.get(transition.getSource());
292                    Integer value = transitionMap.get(transition);
293                    if (value == null) {
294                        value = 0;
295                    }
296                    transitionMap.put(transition, value + 1);
297                }
298                else {
299                    throw new RuntimeException(
300                                               "Wrong event type. Only UMLTransitionType supported but was: " +
301                                                   event.getType().getClass().getName());
302                }
303            }
304        }
305
306        // calculate probabilities
307        for (Region region : stateMachine.getRegions()) {
308            for (Vertex state : region.getSubvertices()) {
309                Map<Transition, Integer> transitionMap = stateMap.get(state);
310                int totalCount = 0;
311                for (Entry<Transition, Integer> entry : transitionMap.entrySet()) {
312                    totalCount += entry.getValue();
313                }
314                if (totalCount != 0) {
315                    for (Transition transition : state.getOutgoings()) {
316                        double prob = 0.0d;
317                        if (transitionMap.containsKey(transition)) {
318                            prob = ((double) transitionMap.get(transition)) / totalCount;
319                        }
320                        Comment comment = transition.createOwnedComment();
321                        comment.setBody("" + prob);
322                    }
323                }
324                else {
325                    // system has never been in this state, all transitions equally likely
326                    int numOutgoings = state.getOutgoings().size();
327                    for (Transition transition : state.getOutgoings()) {
328                        Comment comment = transition.createOwnedComment();
329                        comment.setBody("" + (1.0d / numOutgoings));
330                    }
331                }
332            }
333        }
334    }
335
336    /**
337     * <p>
338     * Determines all matching {@link Transition} sequences in a state machine for a given sequence
339     * of SOAP events.
340     * </p>
341     *
342     * @param sequence
343     *            SOAP sequence
344     * @param stateMachine
345     *            the state machine
346     * @return all matching {@link Transition} sequences
347     */
348    public static List<List<Transition>> determineMatchingTransitionSequences(List<Event> sequence,
349                                                                              StateMachine stateMachine)
350    {
351        EList<Region> regions = stateMachine.getRegions();
352        EList<Vertex> states = null;
353        for (Region region : regions) {
354            if (states == null) {
355                states = region.getSubvertices();
356            }
357            else {
358                states.addAll(region.getSubvertices());
359            }
360        }
361        List<Transition> allTransitions = new LinkedList<>();
362        for (Vertex state : states) {
363            allTransitions.addAll(state.getOutgoings());
364        }
365
366        List<List<Transition>> matchingSequences = null;
367        List<Transition> currentTransitions = null;
368
369        // first, we try to find a single unique transition that we can match using the method name
370        for (Iterator<Event> eventIterator = sequence.iterator(); eventIterator.hasNext();) {
371            Event event = eventIterator.next();
372            if (event.getType() instanceof SOAPEventType) {
373                SOAPEventType eventType = (SOAPEventType) event.getType();
374                if (matchingSequences == null) {
375                    matchingSequences = new LinkedList<>();
376                    List<Transition> initialMatches = matchTransitions(allTransitions, eventType);
377                    for (Transition transition : initialMatches) {
378                        List<Transition> candidate = new LinkedList<>();
379                        candidate.add(transition);
380                        matchingSequences.add(candidate);
381                    }
382                    currentTransitions = initialMatches;
383                }
384                else {
385                    List<List<Transition>> nextMatchingSequences = new LinkedList<>();
386                    List<Transition> nextCurrentTransitions = new LinkedList<>();
387                    Iterator<Transition> currentTransitionIterator = currentTransitions.iterator();
388                    Iterator<List<Transition>> currentMatchingSequencesIterator =
389                        matchingSequences.iterator();
390                    while (currentTransitionIterator.hasNext()) {
391                        Transition currentTransition = currentTransitionIterator.next();
392                        List<Transition> currentMatch = currentMatchingSequencesIterator.next();
393
394                        List<Transition> matches =
395                            matchTransitions(currentTransition.getTarget().getOutgoings(),
396                                             eventType);
397                        if (matches.isEmpty()) {
398                            throw new RuntimeException("no matches found");
399                        }
400                        for (Transition matchingTransition : matches) {
401                            List<Transition> candidate = new LinkedList<>(currentMatch);
402                            candidate.add(matchingTransition);
403                            nextMatchingSequences.add(candidate);
404                            nextCurrentTransitions.add(matchingTransition);
405                        }
406                    }
407                    matchingSequences = nextMatchingSequences;
408                    currentTransitions = nextCurrentTransitions;
409                }
410            }
411            else {
412                throw new RuntimeException(
413                                           "Wrong event type. Only UMLTransitionType supported but was: " +
414                                               event.getType().getClass().getName());
415            }
416        }
417        return matchingSequences;
418    }
419
420    /**
421     * <p>
422     * Extends a given model with an interaction that represents an observed sequence.
423     * </p>
424     *
425     * @param sequence
426     *            sequence that is added as sequence diagram
427     * @param model
428     *            UML model to which the interaction is added
429     * @param interactionName
430     *            name of the interaction
431     * @param testContextName
432     *            Name of the test context that should be used. If this value is null, the first
433     *            test context found is used.
434     */
435    public static void createInteractionFromEventSequence(List<Event> sequence,
436                                                          Model model,
437                                                          String interactionName,
438                                                          String testContextName)
439    {
440        final Profile utpProfile = model.getAppliedProfile("utp");
441        final Stereotype utpTestCase = (Stereotype) utpProfile.getOwnedMember("TestCase");
442        final Stereotype utpTestComponent = (Stereotype) utpProfile.getOwnedMember("TestComponent");
443        final Stereotype utpSUT = (Stereotype) utpProfile.getOwnedMember("SUT");
444        final Stereotype utpTestContext = (Stereotype) utpProfile.getOwnedMember("TestContext");
445
446        Component testContext = fetchTestContext(model, utpTestContext, testContextName);
447        if (testContext == null) {
448            throw new RuntimeException("Could not find any test context in the model");
449        }
450
451        Operation operation = testContext.createOwnedOperation(interactionName, null, null);
452        operation.applyStereotype(utpTestCase);
453
454        Interaction interaction =
455            (Interaction) testContext.createPackagedElement(interactionName + "_Impl",
456                                                            UMLPackage.Literals.INTERACTION);
457        operation.getMethods().add(interaction);
458
459        // create lifelines
460        Lifeline userLifeline = null;
461
462        for (Property property : testContext.getAllAttributes()) {
463            if (property.getAppliedStereotypes().contains(utpSUT)) {
464                String serviceName = property.getName();
465                Lifeline targetLifeline = interaction.createLifeline(serviceName);
466                targetLifeline.setRepresents(property);
467            }
468            else if (property.getType().getAppliedStereotypes().contains(utpTestComponent)) {
469                if (userLifeline != null) {
470                    throw new RuntimeException(
471                                               "TestContext must only have one TestComponent for the application of usage-based testing.");
472                }
473                userLifeline = interaction.createLifeline(property.getName());
474                userLifeline.setRepresents(property);
475            }
476        }
477        if (userLifeline == null) {
478            throw new RuntimeException("No TestComponent found, could not create user lifeline.");
479        }
480        if (interaction.getLifelines().size() < 2) {
481            throw new RuntimeException("Fewer than two lifelines created. No SUT found.");
482        }
483
484        int i = 0;
485        for (Event event : sequence) {
486            if (!(event.equals(Event.STARTEVENT) || event.equals(Event.ENDEVENT))) {
487                String serviceName = getServiceNameFromEvent(event);
488                String methodName = getCalledMethodFromEvent(event);
489                // determine lifelines
490                Lifeline msgTargetLifeline;
491                Lifeline msgSourceLifeline;
492
493                if (serviceName.equals(userLifeline.getName())) {
494                    // message being send to user
495                    // currently we just select the first lifeline that is not the user
496                    // this, obviously, has to be replaced with the real service.
497                    // however, identification of the source of a message is still an open issue
498                    msgSourceLifeline = null;
499                    for (Lifeline lifeline : interaction.getLifelines()) {
500                        if (!lifeline.equals(userLifeline)) {
501                            msgSourceLifeline = lifeline;
502                            break;
503                        }
504                    }
505                    msgTargetLifeline = userLifeline;
506                }
507                else {
508                    msgSourceLifeline = userLifeline;
509                    msgTargetLifeline = interaction.getLifeline(serviceName);
510                }
511                if (msgSourceLifeline == null) {
512                    throw new RuntimeException(
513                                               "Error creating message: could not determine source lifeline.");
514                }
515                if (msgTargetLifeline == null) {
516                    throw new RuntimeException(
517                                               "Error creating message: could not determine target lifeline.");
518                }
519                // determine correct target interface
520                Set<Interface> targetInterfaces =
521                    getRealizedInterfacesFromProperty((Property) msgTargetLifeline.getRepresents());
522                if (targetInterfaces.isEmpty()) {
523                    throw new RuntimeException("no interface associated with the property " +
524                        msgTargetLifeline.getRepresents().getName());
525                }
526                Interface targetInterface = null;
527                for (Interface intface : targetInterfaces) {
528                    if (getOperationFromName(intface.getOperations(), methodName) != null) {
529                        // interface found
530                        targetInterface = intface;
531                        break;
532                    }
533                }
534                if (targetInterface == null) {
535                    StringBuilder errStrBuilder = new StringBuilder();
536                    errStrBuilder
537                        .append("Error creating message: operation not found in the implementing interfaces (");
538                    Iterator<Interface> iter = targetInterfaces.iterator();
539                    while (iter.hasNext()) {
540                        String interfaceName = iter.next().getName();
541                        errStrBuilder.append(interfaceName);
542                        if (iter.hasNext()) {
543                            errStrBuilder.append(",");
544                        }
545                        else {
546                            errStrBuilder.append("): " + methodName);
547                        }
548                    }
549                    throw new RuntimeException(errStrBuilder.toString());
550                }
551
552                Operation calledOperation =
553                    getOperationFromName(targetInterface.getOperations(), methodName);
554
555                // setup for both SYNCH and ASYNCH calls
556                MessageOccurrenceSpecification callSendFragment =
557                    (MessageOccurrenceSpecification) interaction
558                        .createFragment(i + ":" + methodName + "_callSendFragment",
559                                        UMLPackage.Literals.MESSAGE_OCCURRENCE_SPECIFICATION);
560                MessageOccurrenceSpecification callRecvFragment =
561                    (MessageOccurrenceSpecification) interaction
562                        .createFragment(i + ":" + methodName + "_callRecvFragment",
563                                        UMLPackage.Literals.MESSAGE_OCCURRENCE_SPECIFICATION);
564
565                callSendFragment.setCovered(msgSourceLifeline);
566                callRecvFragment.setCovered(msgTargetLifeline);
567
568                // create call
569                Message callMessage = interaction.createMessage(methodName);
570                callMessage.setSignature(calledOperation);
571                callMessage.setConnector(inferConnector(msgSourceLifeline, msgTargetLifeline));
572                callMessage.setSendEvent(callSendFragment);
573                callMessage.setReceiveEvent(callRecvFragment);
574                callSendFragment.setMessage(callMessage);
575                callRecvFragment.setMessage(callMessage);
576
577                boolean asynch = false;
578                if (calledOperation.getConcurrency() == CallConcurrencyKind.CONCURRENT_LITERAL) {
579                    asynch = true;
580                }
581                if (asynch) {
582                    // Create ASYNCH call
583                    callMessage.setMessageSort(MessageSort.ASYNCH_CALL_LITERAL);
584                }
585                else {
586                    // SYNCH call
587                    callMessage.setMessageSort(MessageSort.SYNCH_CALL_LITERAL);
588
589                    // setup reply and behavior execution specifications
590                    MessageOccurrenceSpecification replySendFragment =
591                        (MessageOccurrenceSpecification) interaction
592                            .createFragment(i + ":" + methodName + "_replySendFragment",
593                                            UMLPackage.Literals.MESSAGE_OCCURRENCE_SPECIFICATION);
594                    MessageOccurrenceSpecification replyRecvFragment =
595                        (MessageOccurrenceSpecification) interaction
596                            .createFragment(i + ":" + methodName + "_replyRecvFragment",
597                                            UMLPackage.Literals.MESSAGE_OCCURRENCE_SPECIFICATION);
598
599                    replySendFragment.setCovered(msgTargetLifeline);
600                    replyRecvFragment.setCovered(msgSourceLifeline);
601
602                    /*
603                     * BehaviorExecutionSpecification sourceBehaviorExecutionSpecification =
604                     * (BehaviorExecutionSpecification) interaction .createFragment(":" + methodName
605                     * + "_sourceBhvExecSpec",
606                     * UMLPackage.Literals.BEHAVIOR_EXECUTION_SPECIFICATION);
607                     * BehaviorExecutionSpecification targetBehaviorExecutionSpecification =
608                     * (BehaviorExecutionSpecification) interaction .createFragment(":" + methodName
609                     * + "_targetBhvExecSpec",
610                     * UMLPackage.Literals.BEHAVIOR_EXECUTION_SPECIFICATION);
611                     *
612                     * sourceBehaviorExecutionSpecification.setStart(callSendFragment);
613                     * sourceBehaviorExecutionSpecification.setFinish(replyRecvFragment);
614                     * targetBehaviorExecutionSpecification.setStart(callRecvFragment);
615                     * targetBehaviorExecutionSpecification.setFinish(replySendFragment);
616                     */
617
618                    // create reply
619                    Message replyMessage = interaction.createMessage(methodName + "_reply");
620                    replyMessage.setMessageSort(MessageSort.REPLY_LITERAL);
621                    replyMessage.setSignature(calledOperation);
622                    replyMessage.setSendEvent(replySendFragment);
623                    replyMessage.setReceiveEvent(replyRecvFragment);
624                    replySendFragment.setMessage(replyMessage);
625                    replyRecvFragment.setMessage(replyMessage);
626                }
627
628                i++;
629            }
630        }
631    }
632
633    /**
634     * <p>
635     * Calculates the usage score of an interaction as the logsum of the event probabilities
636     * multiplied with the length of the interaction.
637     * </p>
638     *
639     * @param interaction
640     *            interaction for which the score is calculated
641     * @param usageProfile
642     *            usage profile used for the calculation
643     * @return calculated usage score
644     */
645    public static double calculateUsageScore(Interaction interaction,
646                                             IStochasticProcess usageProfile)
647    {
648        double usageScore = 0.0d;
649
650        EList<InteractionFragment> interactionFragments = interaction.getFragments();
651        List<Event> eventSequence = new LinkedList<>();
652        eventSequence.add(Event.STARTEVENT);
653        for (InteractionFragment interactionFragment : interactionFragments) {
654            if (interactionFragment.getName() != null &&
655                interactionFragment.getName().endsWith("_recvFragment")) // TODO must be more
656                                                                         // generic
657            {
658                String serviceName =
659                    interactionFragment.getCovereds().get(0).getRepresents().getName().split("_")[0];
660                String methodName = "UNKNOWN";
661                if (interactionFragment instanceof MessageOccurrenceSpecification) {
662                    methodName =
663                        ((MessageOccurrenceSpecification) interactionFragment).getMessage()
664                            .getName();
665                }
666                eventSequence.add(new Event(new SimpleSOAPEventType(methodName, serviceName)));
667            }
668        }
669        eventSequence.add(Event.ENDEVENT);
670        double prob = usageProfile.getLogSum(eventSequence);
671        usageScore = eventSequence.size() * prob;
672
673        return usageScore;
674    }
675
676    /**
677     * <p>
678     * Extends the given model with an activity for usage-based scheduling of the test cases.
679     * </p>
680     *
681     * @param model
682     *            model to be extended
683     * @param usageProfile
684     *            usage profile used as foundation
685     */
686    public static void createScheduling(Model model,
687                                        IStochasticProcess usageProfile,
688                                        String testContextName)
689    {
690
691        final Profile utpProfile = model.getAppliedProfile("utp");
692        final Stereotype utpTestCase = (Stereotype) utpProfile.getOwnedMember("TestCase");
693        final Stereotype utpTestContext = (Stereotype) utpProfile.getOwnedMember("TestContext");
694
695        Component testContext = fetchTestContext(model, utpTestContext, testContextName);
696        if (testContext == null) {
697            throw new RuntimeException("Could not find any test context in the model");
698        }
699
700        Map<Operation, Double> usageScoreMapUnsorted = new HashMap<>();
701
702        // first, we determine all test cases and calculate their usage scores
703        for (Operation operation : testContext.getAllOperations()) {
704            if (operation.getAppliedStereotypes().contains(utpTestCase)) {
705                Interaction interaction = (Interaction) operation.getMethods().get(0);
706                usageScoreMapUnsorted
707                    .put(operation, calculateUsageScore(interaction, usageProfile));
708            }
709        }
710        Map<Operation, Double> usageScoreMapSorted = sortByValue(usageScoreMapUnsorted);
711
712        // now we create the scheduling
713        Activity schedulingActivity =
714            (Activity) testContext.createOwnedBehavior("UsageBasedScheduling",
715                                                       UMLPackage.Literals.ACTIVITY);
716        testContext.setClassifierBehavior(schedulingActivity);
717
718        ActivityNode startNode =
719            schedulingActivity.createOwnedNode("final", UMLPackage.Literals.INITIAL_NODE);
720        ActivityNode finalNode =
721            schedulingActivity.createOwnedNode("final", UMLPackage.Literals.ACTIVITY_FINAL_NODE);
722
723        ActivityNode currentOperationNode = startNode;
724
725        for (Entry<Operation, Double> entry : usageScoreMapSorted.entrySet()) {
726            Operation operation = entry.getKey();
727            CallOperationAction nextOperationNode =
728                (CallOperationAction) schedulingActivity
729                    .createOwnedNode(operation.getName(), UMLPackage.Literals.CALL_OPERATION_ACTION);
730            nextOperationNode.setOperation(operation);
731
732            ActivityEdge edge =
733                schedulingActivity.createEdge(currentOperationNode.getName() + "_to_" +
734                    nextOperationNode.getName(), UMLPackage.Literals.CONTROL_FLOW);
735            edge.setSource(currentOperationNode);
736            edge.setTarget(nextOperationNode);
737
738            currentOperationNode = nextOperationNode;
739        }
740
741        ActivityEdge edge =
742            schedulingActivity
743                .createEdge(currentOperationNode.getName() + "_to_" + finalNode.getName(),
744                            UMLPackage.Literals.CONTROL_FLOW);
745        edge.setSource(currentOperationNode);
746        edge.setTarget(finalNode);
747    }
748
749    /**
750     * From
751     * http://stackoverflow.com/questions/109383/how-to-sort-a-mapkey-value-on-the-values-in-java
752     * and adapted to do an inverse sorting
753     */
754    public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) {
755        List<Map.Entry<K, V>> list = new LinkedList<>(map.entrySet());
756        Collections.sort(list, new Comparator<Map.Entry<K, V>>() {
757            @Override
758            public int compare(Map.Entry<K, V> o1, Map.Entry<K, V> o2) {
759                return -1 * (o1.getValue()).compareTo(o2.getValue());
760            }
761        });
762
763        Map<K, V> result = new LinkedHashMap<>();
764        for (Map.Entry<K, V> entry : list) {
765            result.put(entry.getKey(), entry.getValue());
766        }
767        return result;
768    }
769
770    /**
771     * <p>
772     * Helper function to get the name of a service from a SOAP event.
773     * </p>
774     *
775     * @param event
776     *            event for which the service name is retrieved
777     * @return service name
778     */
779    protected static String getServiceNameFromEvent(Event event) {
780        if (event.getType() instanceof SOAPEventType) {
781            return ((SOAPEventType) event.getType()).getServiceName();
782        }
783        else if (event.getType() instanceof SimpleSOAPEventType) {
784            return ((SimpleSOAPEventType) event.getType()).getServiceName();
785        }
786        else {
787            throw new RuntimeException(
788                                       "Wrong event type. Only SOAPEventType and SimpleSOAPEventType supported but was: " +
789                                           event.getType().getClass().getName());
790        }
791    }
792
793    /**
794     *
795     * <p>
796     * Helper function to get the called method from a SOAP event
797     * </p>
798     *
799     * @param event
800     *            event for which the called method is retrieved
801     * @return called method
802     */
803    private static String getCalledMethodFromEvent(Event event) {
804        if (event.getType() instanceof SOAPEventType) {
805            return ((SOAPEventType) event.getType()).getCalledMethod();
806        }
807        else if (event.getType() instanceof SimpleSOAPEventType) {
808            return ((SimpleSOAPEventType) event.getType()).getCalledMethod();
809        }
810        else {
811            throw new RuntimeException(
812                                       "Wrong event type. Only SOAPEventType and SimpleSOAPEventType supported but was: " +
813                                           event.getType().getClass().getName());
814        }
815    }
816
817    /**
818     * <p>
819     * Fetches an operation using only its name from a list of operations. Returns the first match
820     * found or null if no match is found.
821     * </p>
822     *
823     * @param operations
824     *            list of operations
825     * @param name
826     *            name of the operation
827     * @return first matching operation; null if no match is found
828     */
829    private static Operation getOperationFromName(EList<Operation> operations, String name) {
830        if (name == null) {
831            throw new IllegalArgumentException("name of the operation must not be null");
832        }
833        if (operations != null) {
834            for (Operation operation : operations) {
835                if (operation.getName() != null && operation.getName().equals(name)) {
836                    return operation;
837                }
838            }
839        }
840        return null;
841    }
842
843    /**
844     * <p>
845     * Determines which transitions match a given {@link SOAPEventType}.
846     * </p>
847     *
848     * @param transitions
849     *            the transitions
850     * @param eventType
851     *            the SOAP event
852     * @return matching transitions
853     */
854    private static List<Transition> matchTransitions(List<Transition> transitions,
855                                                     SOAPEventType eventType)
856    {
857        List<Transition> matching = new LinkedList<>();
858        for (Transition transition : transitions) {
859            // String serviceName = transition.getName().split("\\.")[0]; // TODO service name check
860            String methodName = transition.getName().split("\\.")[1];
861            if (methodName.equals(eventType.getCalledMethod())) {
862                matching.add(transition);
863            }
864        }
865        return matching;
866    }
867
868    private static Set<Interface> getRealizedInterfacesFromProperty(Property property) {
869        return getRealizedInterfaceFromComponent((Component) property.getType());
870    }
871
872    private static Set<Interface> getRealizedInterfaceFromComponent(Component comp) {
873        Set<Interface> interfaces = new HashSet<>();
874        // Interface myInterface = null;
875        for (Property property : comp.getAttributes()) {
876            if (property instanceof Port) {
877                Port port = (Port) property;
878                if (!port.isConjugated()) {
879                    interfaces.addAll(port.getProvideds());
880                }
881            }
882        }
883        return interfaces;
884    }
885
886    private static Component fetchTestContext(Package pkg,
887                                              Stereotype utpTestContext,
888                                              String testContextName)
889    {
890        List<Component> testContexts = fetchTestContextRecursively(pkg, utpTestContext);
891        if (testContexts.isEmpty()) {
892            return null;
893        }
894        if (testContextName != null) {
895            for (Component testContext : testContexts) {
896                if (testContextName.equals(testContext.getName())) {
897                    return testContext;
898                }
899            }
900            return null;
901        }
902        else {
903            return testContexts.get(0);
904        }
905    }
906
907    private static List<Component> fetchTestContextRecursively(Package pkg,
908                                                               Stereotype utpTestContext)
909    {
910        List<Component> testContexts = new LinkedList<>();
911        for (Element element : pkg.getOwnedElements()) {
912            if (element instanceof Package) {
913                testContexts.addAll(fetchTestContextRecursively((Package) element, utpTestContext));
914            }
915            if (element instanceof Component &&
916                element.getAppliedStereotypes().contains(utpTestContext))
917            {
918                testContexts.add((Component) element);
919            }
920        }
921        return testContexts;
922    }
923
924    /**
925     * <p>
926     * Infers connector between two lifelines. TODO: currently assumes only one connector between
927     * two lifelines possible. I need to make sure this assumption is valid.
928     * </p>
929     *
930     * @param userAttributes
931     * @param targetAttributes
932     */
933    private static Connector inferConnector(Lifeline msgSourceLifeline, Lifeline msgTargetLifeline)
934    {
935        EList<Property> userAttributes =
936            ((Component) msgSourceLifeline.getRepresents().getType()).getAttributes();
937        EList<Property> targetAttributes =
938            ((Component) msgTargetLifeline.getRepresents().getType()).getAttributes();
939        for (Property userAttribute : userAttributes) {
940            if (userAttribute instanceof Port) {
941                EList<ConnectorEnd> userEnds = ((Port) userAttribute).getEnds();
942                for (ConnectorEnd userEnd : userEnds) {
943                    Connector userConnector = (Connector) userEnd.eContainer();
944                    for (Property targetAttribute : targetAttributes) {
945                        if (targetAttribute instanceof Port) {
946                            EList<ConnectorEnd> targetEnds = ((Port) targetAttribute).getEnds();
947                            for (ConnectorEnd targetEnd : targetEnds) {
948                                Connector targetConnector = (Connector) targetEnd.eContainer();
949                                if (targetConnector == userConnector) {
950                                    return targetConnector;
951                                }
952                            }
953                        }
954                    }
955                }
956            }
957        }
958        return null;
959    }
960}
Note: See TracBrowser for help on using the repository browser.