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

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