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

Last change on this file since 1763 was 1763, checked in by sherbold, 10 years ago
  • updated test to newer DSL model version
  • added createScheduling functionality to UMLUtils
  • Property svn:mime-type set to text/plain
File size: 26.0 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.Iterator;
22import java.util.LinkedHashMap;
23import java.util.LinkedList;
24import java.util.List;
25import java.util.Map;
26import java.util.Map.Entry;
27
28import org.eclipse.emf.common.util.EList;
29import org.eclipse.uml2.uml.Activity;
30import org.eclipse.uml2.uml.ActivityEdge;
31import org.eclipse.uml2.uml.ActivityNode;
32import org.eclipse.uml2.uml.CallOperationAction;
33import org.eclipse.uml2.uml.Comment;
34import org.eclipse.uml2.uml.Component;
35import org.eclipse.uml2.uml.Connector;
36import org.eclipse.uml2.uml.ConnectorEnd;
37import org.eclipse.uml2.uml.Interaction;
38import org.eclipse.uml2.uml.InteractionFragment;
39import org.eclipse.uml2.uml.Interface;
40import org.eclipse.uml2.uml.Lifeline;
41import org.eclipse.uml2.uml.Message;
42import org.eclipse.uml2.uml.MessageOccurrenceSpecification;
43import org.eclipse.uml2.uml.MessageSort;
44import org.eclipse.uml2.uml.Model;
45import org.eclipse.uml2.uml.Operation;
46import org.eclipse.uml2.uml.Port;
47import org.eclipse.uml2.uml.Profile;
48import org.eclipse.uml2.uml.Property;
49import org.eclipse.uml2.uml.Realization;
50import org.eclipse.uml2.uml.Region;
51import org.eclipse.uml2.uml.StateMachine;
52import org.eclipse.uml2.uml.Stereotype;
53import org.eclipse.uml2.uml.Transition;
54import org.eclipse.uml2.uml.UMLPackage;
55import org.eclipse.uml2.uml.Vertex;
56
57import de.ugoe.cs.autoquest.eventcore.Event;
58import de.ugoe.cs.autoquest.plugin.http.eventcore.SOAPEventType;
59import de.ugoe.cs.autoquest.plugin.http.eventcore.SimpleSOAPEventType;
60import de.ugoe.cs.autoquest.plugin.uml.eventcore.UMLTransitionType;
61import de.ugoe.cs.autoquest.usageprofiles.IStochasticProcess;
62
63/**
64 * <p>
65 * Utilities for working with UML.
66 * </p>
67 *
68 * @author Steffen Herbold
69 */
70public class UMLUtils {
71
72    /**
73     * <p>
74     * Creates a sequence of events with {@link UMLTransitionType} as event type from a given
75     * sequence of events with the {@link SOAPEventType}, by matching the sequences to a state
76     * machine.
77     * </p>
78     *
79     * @param sequence
80     *            SOAP sequences
81     * @param stateMachine
82     *            the state machine
83     * @return create UML sequences
84     */
85    public static List<Event> createUMLTransitionSequence(List<Event> sequence,
86                                                          StateMachine stateMachine)
87    {
88        List<List<Transition>> matchingSequences =
89            determineMatchingTransitionSequences(sequence, stateMachine);
90
91        if (matchingSequences.size() != 1) {
92            throw new RuntimeException("no unique match found; " + matchingSequences.size() +
93                " matches");
94        }
95        List<Event> umlEventSequence = new LinkedList<>();
96        for (Transition transition : matchingSequences.get(0)) {
97            umlEventSequence.add(new Event(new UMLTransitionType(transition)));
98        }
99        return umlEventSequence;
100    }
101
102    /**
103     * <p>
104     * Uses a sequences of events with the {@link UMLTransitionType} to determine the transition
105     * probabilities for the state machine.
106     * </p>
107     *
108     * @param sequences
109     *            UML sequences
110     * @param stateMachine
111     *            state machine to be converted to a usage profile
112     */
113    public static void convertStateMachineToUsageProfile(Collection<List<Event>> sequences,
114                                                         StateMachine stateMachine)
115    {
116        // create state->outgoings hashmap
117        Map<Vertex, Map<Transition, Integer>> stateMap = new HashMap<>();
118        for (Region region : stateMachine.getRegions()) {
119            for (Vertex state : region.getSubvertices()) {
120                stateMap.put(state, new HashMap<Transition, Integer>());
121            }
122        }
123
124        // create counters for each transition
125        for (List<Event> sequence : sequences) {
126            for (Event event : sequence) {
127                if (event.getType() instanceof UMLTransitionType) {
128                    Transition transition = ((UMLTransitionType) event.getType()).getTransition();
129                    Map<Transition, Integer> transitionMap = stateMap.get(transition.getSource());
130                    Integer value = transitionMap.get(transition);
131                    if (value == null) {
132                        value = 0;
133                    }
134                    transitionMap.put(transition, value + 1);
135                }
136                else {
137                    throw new RuntimeException(
138                                               "Wrong event type. Only UMLTransitionType supported but was: " +
139                                                   event.getType().getClass().getName());
140                }
141            }
142        }
143
144        // calculate probabilities
145        for (Region region : stateMachine.getRegions()) {
146            for (Vertex state : region.getSubvertices()) {
147                Map<Transition, Integer> transitionMap = stateMap.get(state);
148                int totalCount = 0;
149                for (Entry<Transition, Integer> entry : transitionMap.entrySet()) {
150                    totalCount += entry.getValue();
151                }
152                if (totalCount != 0) {
153                    for (Transition transition : state.getOutgoings()) {
154                        double prob = 0.0d;
155                        if (transitionMap.containsKey(transition)) {
156                            prob = ((double) transitionMap.get(transition)) / totalCount;
157                        }
158                        Comment comment = transition.createOwnedComment();
159                        comment.setBody("" + prob);
160                    }
161                }
162                else {
163                    // system has never been in this state, all transitions equally likely
164                    int numOutgoings = state.getOutgoings().size();
165                    for (Transition transition : state.getOutgoings()) {
166                        Comment comment = transition.createOwnedComment();
167                        comment.setBody("" + (1.0d / numOutgoings));
168                    }
169                }
170            }
171        }
172    }
173
174    /**
175     * <p>
176     * Determines all matching {@link Transition} sequences in a state machine for a given sequence
177     * of SOAP events.
178     * </p>
179     *
180     * @param sequence
181     *            SOAP sequence
182     * @param stateMachine
183     *            the state machine
184     * @return all matching {@link Transition} sequences
185     */
186    public static List<List<Transition>> determineMatchingTransitionSequences(List<Event> sequence,
187                                                                              StateMachine stateMachine)
188    {
189        EList<Region> regions = stateMachine.getRegions();
190        EList<Vertex> states = null;
191        for (Region region : regions) {
192            if (states == null) {
193                states = region.getSubvertices();
194            }
195            else {
196                states.addAll(region.getSubvertices());
197            }
198        }
199        List<Transition> allTransitions = new LinkedList<>();
200        for (Vertex state : states) {
201            allTransitions.addAll(state.getOutgoings());
202        }
203
204        List<List<Transition>> matchingSequences = null;
205        List<Transition> currentTransitions = null;
206
207        // first, we try to find a single unique transition that we can match using the method name
208        for (Iterator<Event> eventIterator = sequence.iterator(); eventIterator.hasNext();) {
209            Event event = eventIterator.next();
210            if (event.getType() instanceof SOAPEventType) {
211                SOAPEventType eventType = (SOAPEventType) event.getType();
212                if (matchingSequences == null) {
213                    matchingSequences = new LinkedList<>();
214                    List<Transition> initialMatches = matchTransitions(allTransitions, eventType);
215                    for (Transition transition : initialMatches) {
216                        List<Transition> candidate = new LinkedList<>();
217                        candidate.add(transition);
218                        matchingSequences.add(candidate);
219                    }
220                    currentTransitions = initialMatches;
221                }
222                else {
223                    List<List<Transition>> nextMatchingSequences = new LinkedList<>();
224                    List<Transition> nextCurrentTransitions = new LinkedList<>();
225                    Iterator<Transition> currentTransitionIterator = currentTransitions.iterator();
226                    Iterator<List<Transition>> currentMatchingSequencesIterator =
227                        matchingSequences.iterator();
228                    while (currentTransitionIterator.hasNext()) {
229                        Transition currentTransition = currentTransitionIterator.next();
230                        List<Transition> currentMatch = currentMatchingSequencesIterator.next();
231
232                        List<Transition> matches =
233                            matchTransitions(currentTransition.getTarget().getOutgoings(),
234                                             eventType);
235                        if (matches.isEmpty()) {
236                            throw new RuntimeException("no matches found");
237                        }
238                        for (Transition matchingTransition : matches) {
239                            List<Transition> candidate = new LinkedList<>(currentMatch);
240                            candidate.add(matchingTransition);
241                            nextMatchingSequences.add(candidate);
242                            nextCurrentTransitions.add(matchingTransition);
243                        }
244                    }
245                    matchingSequences = nextMatchingSequences;
246                    currentTransitions = nextCurrentTransitions;
247                }
248            }
249            else {
250                throw new RuntimeException(
251                                           "Wrong event type. Only UMLTransitionType supported but was: " +
252                                               event.getType().getClass().getName());
253            }
254        }
255        return matchingSequences;
256    }
257
258    /**
259     * <p>
260     * Extends a given model with an interaction that represents an observed sequence.
261     * </p>
262     *
263     * @param sequence
264     *            sequence that is added as sequence diagram
265     * @param model
266     *            UML model to which the interaction is added
267     * @param interactionName
268     *            name of the interaction
269     */
270    public static void createInteractionFromEventSequence(List<Event> sequence,
271                                                          Model model,
272                                                          String interactionName)
273    {
274        Map<String, Port> portMap = new HashMap<>();
275
276        Component testContext =
277            (Component) model.getPackagedElement("TestContext", true,
278                                                 UMLPackage.Literals.COMPONENT, true);
279
280        final Profile utpProfile = model.getAppliedProfile("utp");
281        final Stereotype utpTestCase = (Stereotype) utpProfile.getOwnedMember("TestCase");
282        final Stereotype utpTestComponent = (Stereotype) utpProfile.getOwnedMember("TestComponent");
283        final Stereotype utpSUT = (Stereotype) utpProfile.getOwnedMember("SUT");
284       
285        Operation operation = testContext.createOwnedOperation(interactionName, null, null);
286        operation.applyStereotype(utpTestCase);
287
288        Interaction interaction =
289            (Interaction) testContext.createPackagedElement(interactionName + "_Impl",
290                                                            UMLPackage.Literals.INTERACTION);
291        operation.getMethods().add(interaction);
292       
293        // create lifelines
294        Lifeline userLifeline = null;
295        List<Port> userPorts = new LinkedList<>();
296        for (Property property : testContext.getAllAttributes()) {
297            if (property.getAppliedStereotypes().contains(utpSUT)) {
298                String serviceName = getRealizedInterfaceFromProperty(property).getName();
299               
300                Lifeline targetLifeline = interaction.createLifeline(serviceName);
301                targetLifeline.setRepresents(property);
302                portMap.put(serviceName,
303                            (Port) ((Component) property.getType()).getAttribute("p_" + serviceName, null));
304            }
305            else if (property.getType().getAppliedStereotypes().contains(utpTestComponent)) {
306                userLifeline = interaction.createLifeline(property.getName());
307                userLifeline.setRepresents(property);
308                EList<Property> userAttributes = ((Component) property.getType()).getAttributes();
309                for (Property userAttribute : userAttributes) {
310                    if (userAttribute instanceof Port) {
311                        userPorts.add((Port) userAttribute);
312                    }
313                }
314            }
315        }
316
317        int i = 0;
318        for (Event event : sequence) {
319            if (!(event.equals(Event.STARTEVENT) || event.equals(Event.ENDEVENT))) {
320                String serviceName = getServiceNameFromEvent(event);
321                String methodName = getCalledMethodFromEvent(event);
322
323                Lifeline targetLifeline = interaction.getLifeline(serviceName);
324                Interface targetInterface = getRealizedInterfaceFromProperty((Property) targetLifeline.getRepresents());
325
326                MessageOccurrenceSpecification sendFragment =
327                    (MessageOccurrenceSpecification) interaction
328                        .createFragment(i + ":" + methodName + "_sendFragment",
329                                        UMLPackage.Literals.MESSAGE_OCCURRENCE_SPECIFICATION);
330                MessageOccurrenceSpecification recvFragment =
331                    (MessageOccurrenceSpecification) interaction
332                        .createFragment(i + ":" + methodName + "_recvFragment",
333                                        UMLPackage.Literals.MESSAGE_OCCURRENCE_SPECIFICATION);
334
335                sendFragment.setCovered(userLifeline);
336                recvFragment.setCovered(targetLifeline);
337
338                Message message = interaction.createMessage(methodName);
339                if (getOperationFromName(targetInterface.getOperations(), methodName) == null) {
340                    System.out.println("operation not found: " + methodName);
341                }
342                message.setSignature(getOperationFromName(targetInterface.getOperations(),
343                                                          methodName));
344                message.setMessageSort(MessageSort.ASYNCH_CALL_LITERAL);
345                message.setSendEvent(sendFragment);
346                message.setReceiveEvent(recvFragment);
347
348                EList<ConnectorEnd> targetEnds = portMap.get(serviceName).getEnds();
349
350                for (Port userPort : userPorts) {
351                    EList<ConnectorEnd> sourceEnds = userPort.getEnds();
352                    for (ConnectorEnd sourceEnd : sourceEnds) {
353                        Connector sourceConnector = (Connector) sourceEnd.eContainer();
354                        for (ConnectorEnd targetEnd : targetEnds) {
355                            Connector targetConnector = (Connector) targetEnd.eContainer();
356                            if (targetConnector == sourceConnector) {
357                                message.setConnector(targetConnector);
358                            }
359                        }
360                    }
361                }
362
363                sendFragment.setMessage(message);
364                recvFragment.setMessage(message);
365
366                i++;
367            }
368        }
369    }
370
371    /**
372     * <p>
373     * Calculates the usage score of an interaction as the logsum of the event probabilities
374     * multiplied with the length of the interaction.
375     * </p>
376     *
377     * @param interaction
378     *            interaction for which the score is calculated
379     * @param usageProfile
380     *            usage profile used for the calculation
381     * @return calculated usage score
382     */
383    public static double calculateUsageScore(Interaction interaction,
384                                             IStochasticProcess usageProfile)
385    {
386        double usageScore = 0.0d;
387
388        EList<InteractionFragment> interactionFragments = interaction.getFragments();
389        List<Event> eventSequence = new LinkedList<>();
390        eventSequence.add(Event.STARTEVENT);
391        for (InteractionFragment interactionFragment : interactionFragments) {
392            if (interactionFragment.getName() != null &&
393                interactionFragment.getName().endsWith("_recvFragment"))
394            {
395                String serviceName =
396                    interactionFragment.getCovereds().get(0).getRepresents().getName().split("_")[0];
397                String methodName = "UNKNOWN";
398                if (interactionFragment instanceof MessageOccurrenceSpecification) {
399                    methodName =
400                        ((MessageOccurrenceSpecification) interactionFragment).getMessage()
401                            .getName();
402                }
403                eventSequence.add(new Event(new SimpleSOAPEventType(methodName, serviceName)));
404            }
405        }
406        eventSequence.add(Event.ENDEVENT);
407        double prob = usageProfile.getLogSum(eventSequence);
408        usageScore = eventSequence.size() * prob;
409
410        return usageScore;
411    }
412
413    /**
414     * <p>
415     * Extends the given model with an activity for usage-based scheduling of the test cases.
416     * </p>
417     *
418     * @param model
419     *            model to be extended
420     * @param usageProfile
421     *            usage profile used as foundation
422     */
423    public static void createScheduling(Model model, IStochasticProcess usageProfile) {
424
425        final Profile utpProfile = model.getAppliedProfile("utp");
426        final Stereotype utpTestCase = (Stereotype) utpProfile.getOwnedMember("TestCase");
427
428        Component testContext =
429            (Component) model.getPackagedElement("TestContext", true,
430                                                 UMLPackage.Literals.COMPONENT, true);
431
432        Map<Operation, Double> usageScoreMapUnsorted = new HashMap<>();
433
434        // first, we determine all test cases and calculate their usage scores
435        for (Operation operation : testContext.getAllOperations()) {
436            if (operation.getAppliedStereotypes().contains(utpTestCase)) {
437                Interaction interaction = (Interaction) operation.getMethods().get(0);
438                usageScoreMapUnsorted
439                    .put(operation, calculateUsageScore(interaction, usageProfile));
440            }
441        }
442        Map<Operation, Double> usageScoreMapSorted = sortByValue(usageScoreMapUnsorted);
443
444        // now we create the scheduling
445        Activity schedulingActivity =
446            (Activity) testContext.createOwnedBehavior("UsageBasedScheduling",
447                                                       UMLPackage.Literals.ACTIVITY);
448        testContext.setClassifierBehavior(schedulingActivity);
449
450        ActivityNode startNode =
451            schedulingActivity.createOwnedNode("final", UMLPackage.Literals.INITIAL_NODE);
452        ActivityNode finalNode =
453            schedulingActivity.createOwnedNode("final", UMLPackage.Literals.ACTIVITY_FINAL_NODE);
454
455        ActivityNode currentOperationNode = startNode;
456
457        for (Entry<Operation, Double> entry : usageScoreMapSorted.entrySet()) {
458            Operation operation = entry.getKey();
459            CallOperationAction nextOperationNode =
460                (CallOperationAction) schedulingActivity
461                    .createOwnedNode(operation.getName(), UMLPackage.Literals.CALL_OPERATION_ACTION);
462            nextOperationNode.setOperation(operation);
463
464            ActivityEdge edge =
465                schedulingActivity.createEdge(currentOperationNode.getName() + "_to_" +
466                    nextOperationNode.getName(), UMLPackage.Literals.CONTROL_FLOW);
467            edge.setSource(currentOperationNode);
468            edge.setTarget(nextOperationNode);
469
470            currentOperationNode = nextOperationNode;
471        }
472
473        ActivityEdge edge =
474            schedulingActivity
475                .createEdge(currentOperationNode.getName() + "_to_" + finalNode.getName(),
476                            UMLPackage.Literals.CONTROL_FLOW);
477        edge.setSource(currentOperationNode);
478        edge.setTarget(finalNode);
479    }
480
481    /**
482     * From
483     * http://stackoverflow.com/questions/109383/how-to-sort-a-mapkey-value-on-the-values-in-java
484     * and adapted to do an inverse sorting
485     */
486    public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) {
487        List<Map.Entry<K, V>> list = new LinkedList<>(map.entrySet());
488        Collections.sort(list, new Comparator<Map.Entry<K, V>>() {
489            @Override
490            public int compare(Map.Entry<K, V> o1, Map.Entry<K, V> o2) {
491                return -1 * (o1.getValue()).compareTo(o2.getValue());
492            }
493        });
494
495        Map<K, V> result = new LinkedHashMap<>();
496        for (Map.Entry<K, V> entry : list) {
497            result.put(entry.getKey(), entry.getValue());
498        }
499        return result;
500    }
501
502    /**
503     * <p>
504     * Helper function to get the name of a service from a SOAP event.
505     * </p>
506     *
507     * @param event
508     *            event for which the service name is retrieved
509     * @return service name
510     */
511    private static String getServiceNameFromEvent(Event event) {
512        if (event.getType() instanceof SOAPEventType) {
513            return ((SOAPEventType) event.getType()).getServiceName();
514        }
515        else if (event.getType() instanceof SimpleSOAPEventType) {
516            return ((SimpleSOAPEventType) event.getType()).getServiceName();
517        }
518        else {
519            throw new RuntimeException(
520                                       "Wrong event type. Only SOAPEventType and SimpleSOAPEventType supported but was: " +
521                                           event.getType().getClass().getName());
522        }
523    }
524
525    /**
526     *
527     * <p>
528     * Helper function to get the called method from a SOAP event
529     * </p>
530     *
531     * @param event
532     *            event for which the called method is retrieved
533     * @return called method
534     */
535    private static String getCalledMethodFromEvent(Event event) {
536        if (event.getType() instanceof SOAPEventType) {
537            return ((SOAPEventType) event.getType()).getCalledMethod();
538        }
539        else if (event.getType() instanceof SimpleSOAPEventType) {
540            return ((SimpleSOAPEventType) event.getType()).getCalledMethod();
541        }
542        else {
543            throw new RuntimeException(
544                                       "Wrong event type. Only SOAPEventType and SimpleSOAPEventType supported but was: " +
545                                           event.getType().getClass().getName());
546        }
547    }
548
549    /**
550     * <p>
551     * Fetches an operation using only its name from a list of operations. Returns the first match
552     * found or null if no match is found.
553     * </p>
554     *
555     * @param operations
556     *            list of operations
557     * @param name
558     *            name of the operation
559     * @return first matching operation; null if no match is found
560     */
561    private static Operation getOperationFromName(EList<Operation> operations, String name) {
562        if (name == null) {
563            throw new IllegalArgumentException("name of the operation must not be null");
564        }
565        if (operations != null) {
566            for (Operation operation : operations) {
567                if (operation.getName() != null && operation.getName().equals(name)) {
568                    return operation;
569                }
570            }
571        }
572        return null;
573    }
574
575    /**
576     * <p>
577     * Determines which transitions match a given {@link SOAPEventType}.
578     * </p>
579     *
580     * @param transitions
581     *            the transitions
582     * @param eventType
583     *            the SOAP event
584     * @return matching transitions
585     */
586    private static List<Transition> matchTransitions(List<Transition> transitions,
587                                                     SOAPEventType eventType)
588    {
589        List<Transition> matching = new LinkedList<>();
590        for (Transition transition : transitions) {
591            // String serviceName = transition.getName().split("\\.")[0]; // TODO service name check
592            String methodName = transition.getName().split("\\.")[1];
593            if (methodName.equals(eventType.getCalledMethod())) {
594                matching.add(transition);
595            }
596        }
597        return matching;
598    }
599   
600    private static Interface getRealizedInterfaceFromProperty(Property property) {
601        return getRealizedInterfaceFromComponent((Component) property.getType());
602    }
603   
604    private static Interface getRealizedInterfaceFromComponent(Component comp) {
605        Realization realization = (Realization) comp.getNestedClassifiers().get(0).getRelationships(UMLPackage.Literals.REALIZATION).get(0);
606        return (Interface) realization.getSuppliers().get(0);
607    }
608
609}
Note: See TracBrowser for help on using the repository browser.