Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
TransitionNotationUml |
|
| 6.523809523809524;6.524 |
1 | /* $Id: TransitionNotationUml.java 18852 2010-11-20 19:27:11Z mvw $ | |
2 | ***************************************************************************** | |
3 | * Copyright (c) 2009-2010 Contributors - see below | |
4 | * All rights reserved. This program and the accompanying materials | |
5 | * are made available under the terms of the Eclipse Public License v1.0 | |
6 | * which accompanies this distribution, and is available at | |
7 | * http://www.eclipse.org/legal/epl-v10.html | |
8 | * | |
9 | * Contributors: | |
10 | * Michiel van der Wulp | |
11 | ***************************************************************************** | |
12 | * | |
13 | * Some portions of this file was previously release using the BSD License: | |
14 | */ | |
15 | ||
16 | // Copyright (c) 2005-2009 The Regents of the University of California. All | |
17 | // Rights Reserved. Permission to use, copy, modify, and distribute this | |
18 | // software and its documentation without fee, and without a written | |
19 | // agreement is hereby granted, provided that the above copyright notice | |
20 | // and this paragraph appear in all copies. This software program and | |
21 | // documentation are copyrighted by The Regents of the University of | |
22 | // California. The software program and documentation are supplied "AS | |
23 | // IS", without any accompanying services from The Regents. The Regents | |
24 | // does not warrant that the operation of the program will be | |
25 | // uninterrupted or error-free. The end-user understands that the program | |
26 | // was developed for research purposes and is advised not to rely | |
27 | // exclusively on the program for any reason. IN NO EVENT SHALL THE | |
28 | // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, | |
29 | // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, | |
30 | // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF | |
31 | // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF | |
32 | // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY | |
33 | // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
34 | // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE | |
35 | // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF | |
36 | // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, | |
37 | // UPDATES, ENHANCEMENTS, OR MODIFICATIONS. | |
38 | ||
39 | package org.argouml.notation.providers.uml; | |
40 | ||
41 | import java.text.ParseException; | |
42 | import java.util.Collection; | |
43 | import java.util.Iterator; | |
44 | import java.util.StringTokenizer; | |
45 | ||
46 | import org.argouml.application.events.ArgoEventPump; | |
47 | import org.argouml.application.events.ArgoEventTypes; | |
48 | import org.argouml.application.events.ArgoHelpEvent; | |
49 | import org.argouml.i18n.Translator; | |
50 | import org.argouml.model.Model; | |
51 | import org.argouml.model.StateMachinesFactory; | |
52 | import org.argouml.notation.NotationSettings; | |
53 | import org.argouml.notation.providers.TransitionNotation; | |
54 | ||
55 | /** | |
56 | * UML Notation for the text shown next to a Transition. | |
57 | * | |
58 | * @author Michiel van der Wulp | |
59 | */ | |
60 | public class TransitionNotationUml extends TransitionNotation { | |
61 | ||
62 | /** | |
63 | * The constructor. | |
64 | * | |
65 | * @param transition the transition represented by this notation | |
66 | */ | |
67 | public TransitionNotationUml(Object transition) { | |
68 | 0 | super(transition); |
69 | 0 | } |
70 | ||
71 | /* | |
72 | * @see org.argouml.uml.notation.NotationProvider#parse(java.lang.Object, java.lang.String) | |
73 | */ | |
74 | public void parse(Object modelElement, String text) { | |
75 | try { | |
76 | 0 | parseTransition(modelElement, text); |
77 | 0 | } catch (ParseException pe) { |
78 | 0 | String msg = "statusmsg.bar.error.parsing.transition"; |
79 | 0 | Object[] args = { |
80 | pe.getLocalizedMessage(), | |
81 | Integer.valueOf(pe.getErrorOffset()), | |
82 | }; | |
83 | 0 | ArgoEventPump.fireEvent(new ArgoHelpEvent( |
84 | ArgoEventTypes.HELP_CHANGED, this, | |
85 | Translator.messageFormat(msg, args))); | |
86 | 0 | } |
87 | 0 | } |
88 | ||
89 | /** | |
90 | * Parse a transition description line of the form:<pre> | |
91 | * "event-signature [guard-condition] / action-expression". | |
92 | * </pre> | |
93 | * | |
94 | * A ";" is not interpreted as having any special meaning. <p> | |
95 | * | |
96 | * The "event-signature" may be one of the 4 | |
97 | * formats:<ul> | |
98 | * <li> ChangeEvent: "when(condition)" | |
99 | * <li> TimeEvent: "after(duration)" | |
100 | * <li> CallEvent: "a(parameter-list)". | |
101 | * <li> SignalEvent: any string without (). | |
102 | * </ul> | |
103 | * | |
104 | * Remark: The UML standard does not make a distinction between | |
105 | * the syntax of a CallEvent and SignalEvent: | |
106 | * both may have parameters between (). | |
107 | * For simplicity and user-friendliness, we chose for this distinction. | |
108 | * If a user wants parameters for a SignalEvent, | |
109 | * then he may add them in the properties panels, but not on the diagram. | |
110 | * <p> | |
111 | * | |
112 | * An alternative solution would be to create a CallEvent by default, | |
113 | * and when editing an existing event, do not change the type.<p> | |
114 | * | |
115 | * TODO: This function fails when the event-signature contains a "[" | |
116 | * or a "/". See issue 5983 for other cases that were | |
117 | * a problem in the past. | |
118 | * | |
119 | * @param trans the transition object to which this string applies | |
120 | * @param s the string to be parsed | |
121 | * @return the transition object | |
122 | * @throws ParseException when no matching [] are found | |
123 | */ | |
124 | protected Object parseTransition(Object trans, String s) | |
125 | throws ParseException { | |
126 | 0 | s = s.trim(); |
127 | ||
128 | 0 | int a = s.indexOf("["); |
129 | 0 | int b = s.indexOf("]"); |
130 | 0 | int c = s.indexOf("/"); |
131 | 0 | if (((a < 0) && (b >= 0)) || ((b < 0) && (a >= 0)) || (b < a)) { |
132 | 0 | String msg = "parsing.error.transition.no-matching-square-brackets"; |
133 | 0 | throw new ParseException(Translator.localize(msg), 0); |
134 | } | |
135 | 0 | if ((c >= 0) && (c < b) && (c > a) && (a > 0)) { |
136 | 0 | String msg = "parsing.error.transition.found-bracket-instead-slash"; |
137 | 0 | throw new ParseException(Translator.localize(msg), 0); |
138 | } | |
139 | ||
140 | 0 | String[] s1 = s.trim().split("/", 2); |
141 | 0 | String eg = s1[0].trim(); |
142 | 0 | String[] s2 = eg.split("\\[", 2); |
143 | ||
144 | 0 | if (s2[0].trim().length() > 0) { |
145 | 0 | parseTrigger(trans, s2[0].trim()); |
146 | } | |
147 | 0 | if (s2.length > 1) { |
148 | 0 | if (s2[1].trim().endsWith("]")) { |
149 | 0 | String g = s2[1].trim(); |
150 | 0 | g = g.substring(0, g.length() - 1).trim(); |
151 | 0 | if (g.length() > 0) { |
152 | 0 | parseGuard(trans, g); |
153 | } | |
154 | } | |
155 | } | |
156 | 0 | if (s1.length > 1) { |
157 | 0 | if (s1[1].trim().length() > 0) { |
158 | 0 | parseEffect(trans, s1[1].trim()); |
159 | } | |
160 | } | |
161 | 0 | return trans; |
162 | } | |
163 | ||
164 | /** | |
165 | * Parse the Event that is the trigger of the given transition. | |
166 | * | |
167 | * @param trans the transition which is triggered by the given event | |
168 | * @param trigger the given trigger | |
169 | * @throws ParseException | |
170 | */ | |
171 | private void parseTrigger(Object trans, String trigger) | |
172 | throws ParseException { | |
173 | // let's look for a TimeEvent, ChangeEvent, CallEvent or SignalEvent | |
174 | 0 | String s = ""; |
175 | 0 | boolean timeEvent = false; |
176 | 0 | boolean changeEvent = false; |
177 | 0 | boolean callEvent = false; |
178 | 0 | boolean signalEvent = false; |
179 | 0 | trigger = trigger.trim(); |
180 | ||
181 | 0 | StringTokenizer tokenizer = new StringTokenizer(trigger, "()"); |
182 | 0 | String name = tokenizer.nextToken().trim(); |
183 | 0 | if (name.equalsIgnoreCase("after")) { |
184 | 0 | timeEvent = true; |
185 | 0 | } else if (name.equalsIgnoreCase("when")) { |
186 | 0 | changeEvent = true; |
187 | } else { | |
188 | // the part after the || is for when there's nothing between the () | |
189 | 0 | if (tokenizer.hasMoreTokens() |
190 | || (trigger.indexOf("(") > 0) | |
191 | || (trigger.indexOf(")") > 1)) { | |
192 | 0 | callEvent = true; |
193 | 0 | if (!trigger.endsWith(")") || !(trigger.indexOf("(") > 0)) { |
194 | 0 | String msg = |
195 | "parsing.error.transition.no-matching-brackets"; | |
196 | 0 | throw new ParseException( |
197 | Translator.localize(msg), 0); | |
198 | } | |
199 | } else { | |
200 | 0 | signalEvent = true; |
201 | } | |
202 | } | |
203 | 0 | if (timeEvent || changeEvent || callEvent) { |
204 | 0 | if (tokenizer.hasMoreTokens()) { |
205 | 0 | s = tokenizer.nextToken().trim(); |
206 | } // else the empty s will do | |
207 | } | |
208 | ||
209 | /* | |
210 | * We can distinguish between 4 cases: | |
211 | * 1. A trigger is given. None exists yet. | |
212 | * 2. The trigger was present, and it is the same type, | |
213 | * or a different type, and its text is changed, or the same. | |
214 | * 3. A trigger is not given. None exists yet. | |
215 | * 4. The name of the trigger was present, but is removed. | |
216 | * The reaction in these cases should be: | |
217 | * 1. Find the referred trigger (issue 5988) or create a new one, and hook it to the transition. | |
218 | * 2. Rename the trigger. | |
219 | * 3. Nop. | |
220 | * 4. Unhook and erase the existing trigger. | |
221 | */ | |
222 | 0 | Object evt = Model.getFacade().getTrigger(trans); |
223 | /* It is safe to give a null to the next function, | |
224 | * since a statemachine is always composed by a model anyhow. */ | |
225 | 0 | Object ns = |
226 | Model.getStateMachinesHelper() | |
227 | .findNamespaceForEvent(trans, null); | |
228 | 0 | StateMachinesFactory sMFactory = |
229 | Model.getStateMachinesFactory(); | |
230 | 0 | boolean weHaveAnEvent = false; |
231 | 0 | if (trigger.length() > 0) { |
232 | // case 1 and 2 | |
233 | 0 | if (evt == null) { |
234 | // case 1 | |
235 | 0 | if (timeEvent) { // after(...) |
236 | 0 | evt = findOrBuildTimeEvent(s, ns); |
237 | /* Do not set the name. */ | |
238 | } | |
239 | 0 | if (changeEvent) { // when(...) |
240 | 0 | evt = findOrBuildChangeEvent(s, ns); |
241 | /* Do not set the name. */ | |
242 | } | |
243 | 0 | if (callEvent) { // operation(paramlist) |
244 | 0 | String triggerName = |
245 | trigger.indexOf("(") > 0 | |
246 | ? trigger.substring(0, trigger.indexOf("(")).trim() | |
247 | : trigger; | |
248 | /* This case is a bit different, because of the parameters. | |
249 | * If the event already exists, the parameters are ignored. */ | |
250 | 0 | evt = findCallEvent(triggerName, ns); |
251 | 0 | if (evt == null) { |
252 | 0 | evt = sMFactory.buildCallEvent(trans, triggerName, ns); |
253 | // and parse the parameter list | |
254 | 0 | NotationUtilityUml.parseParamList(evt, s, 0); |
255 | } | |
256 | } | |
257 | 0 | if (signalEvent) { // signalname |
258 | 0 | evt = findOrBuildSignalEvent(trigger, ns); |
259 | } | |
260 | 0 | weHaveAnEvent = true; |
261 | } else { | |
262 | // case 2 | |
263 | 0 | if (timeEvent) { |
264 | 0 | if (Model.getFacade().isATimeEvent(evt)) { |
265 | /* Just change the time expression */ | |
266 | 0 | Object timeExpr = Model.getFacade().getWhen(evt); |
267 | 0 | if (timeExpr == null) { |
268 | // we have an event without expression | |
269 | 0 | timeExpr = Model.getDataTypesFactory().createTimeExpression("", s); |
270 | 0 | Model.getStateMachinesHelper().setWhen(evt, timeExpr); |
271 | } else { | |
272 | 0 | Model.getDataTypesHelper().setBody(timeExpr, s); |
273 | } | |
274 | 0 | } else { |
275 | /* It's a time-event now, | |
276 | * but was of another type before! */ | |
277 | 0 | delete(evt); /* TODO: What if used elsewhere? */ |
278 | 0 | evt = sMFactory.buildTimeEvent(s, ns); |
279 | 0 | weHaveAnEvent = true; |
280 | } | |
281 | } | |
282 | 0 | if (changeEvent) { |
283 | 0 | if (Model.getFacade().isAChangeEvent(evt)) { |
284 | /* Just change the ChangeExpression */ | |
285 | 0 | Object changeExpr = |
286 | Model.getFacade().getChangeExpression(evt); | |
287 | 0 | if (changeExpr == null) { |
288 | /* Create a new expression: */ | |
289 | 0 | changeExpr = Model.getDataTypesFactory() |
290 | .createBooleanExpression("", s); | |
291 | 0 | Model.getStateMachinesHelper().setExpression(evt, |
292 | changeExpr); | |
293 | } else { | |
294 | 0 | Model.getDataTypesHelper().setBody(changeExpr, s); |
295 | } | |
296 | 0 | } else { |
297 | /* The parsed text describes a change-event, | |
298 | * but the model contains another type! */ | |
299 | 0 | delete(evt); /* TODO: What if used elsewhere? */ |
300 | 0 | evt = sMFactory.buildChangeEvent(s, ns); |
301 | 0 | weHaveAnEvent = true; |
302 | } | |
303 | } | |
304 | 0 | if (callEvent) { |
305 | 0 | if (Model.getFacade().isACallEvent(evt)) { |
306 | /* Just change the Name and linked operation */ | |
307 | 0 | String triggerName = |
308 | trigger.indexOf("(") > 0 | |
309 | ? trigger.substring(0, trigger.indexOf("(")).trim() | |
310 | : trigger; | |
311 | 0 | if (!Model.getFacade().getName(evt) |
312 | .equals(triggerName)) { | |
313 | 0 | Model.getCoreHelper().setName(evt, triggerName); |
314 | } | |
315 | /* TODO: Change the linked operation. */ | |
316 | 0 | } else { |
317 | 0 | delete(evt); /* TODO: What if used elsewhere? */ |
318 | 0 | evt = sMFactory.buildCallEvent(trans, trigger, ns); |
319 | // and parse the parameter list | |
320 | 0 | NotationUtilityUml.parseParamList(evt, s, 0); |
321 | 0 | weHaveAnEvent = true; |
322 | } | |
323 | } | |
324 | 0 | if (signalEvent) { |
325 | 0 | if (Model.getFacade().isASignalEvent(evt)) { |
326 | /* Just change the Name and linked signal */ | |
327 | 0 | if (!Model.getFacade().getName(evt).equals(trigger)) { |
328 | 0 | Model.getCoreHelper().setName(evt, trigger); |
329 | } | |
330 | /* TODO: link to the Signal. */ | |
331 | } else { | |
332 | 0 | delete(evt); /* TODO: What if used elsewhere? */ |
333 | 0 | evt = sMFactory.buildSignalEvent(trigger, ns); |
334 | 0 | weHaveAnEvent = true; |
335 | } | |
336 | } | |
337 | } | |
338 | 0 | if (weHaveAnEvent && (evt != null)) { |
339 | 0 | Model.getStateMachinesHelper().setEventAsTrigger(trans, evt); |
340 | } | |
341 | } else { | |
342 | // case 3 and 4 | |
343 | 0 | if (evt == null) { |
344 | /* case 3 */ | |
345 | } else { | |
346 | // case 4 | |
347 | 0 | delete(evt); // erase it |
348 | } | |
349 | } | |
350 | 0 | } |
351 | ||
352 | protected Object findOrBuildSignalEvent(String trigger, Object ns) { | |
353 | 0 | StateMachinesFactory sMFactory = Model.getStateMachinesFactory(); |
354 | 0 | if ((trigger == null) || ("".equals(trigger.trim()))) { |
355 | 0 | return sMFactory.buildSignalEvent(trigger, ns); |
356 | } | |
357 | 0 | Object result = null; |
358 | 0 | Object type = Model.getMetaTypes().getSignalEvent(); |
359 | 0 | Collection events = Model.getModelManagementHelper() |
360 | .getAllModelElementsOfKind(ns, type); | |
361 | 0 | for (Object event : events) { |
362 | 0 | if (trigger.equals(Model.getFacade().getName(event))) { |
363 | 0 | result = event; |
364 | 0 | break; |
365 | } | |
366 | } | |
367 | 0 | if (result == null) { |
368 | 0 | result = sMFactory.buildSignalEvent(trigger, ns); |
369 | } | |
370 | 0 | return result; |
371 | } | |
372 | ||
373 | protected Object findOrBuildTimeEvent(String timeexpr, Object ns) { | |
374 | 0 | StateMachinesFactory sMFactory = Model.getStateMachinesFactory(); |
375 | 0 | if ((timeexpr == null) || ("".equals(timeexpr.trim()))) { |
376 | 0 | return sMFactory.buildTimeEvent(timeexpr, ns); |
377 | } | |
378 | 0 | Object result = null; |
379 | 0 | Object type = Model.getMetaTypes().getTimeEvent(); |
380 | 0 | Collection events = Model.getModelManagementHelper() |
381 | .getAllModelElementsOfKind(ns, type); | |
382 | 0 | for (Object event : events) { |
383 | 0 | Object expression = Model.getFacade().getExpression(event); |
384 | 0 | if (expression != null) { |
385 | 0 | if (timeexpr.equals(Model.getFacade().getBody(expression))) { |
386 | 0 | result = event; |
387 | 0 | break; |
388 | } | |
389 | } | |
390 | 0 | } |
391 | 0 | if (result == null) { |
392 | 0 | result = sMFactory.buildTimeEvent(timeexpr, ns); |
393 | } | |
394 | 0 | return result; |
395 | } | |
396 | ||
397 | protected Object findOrBuildChangeEvent(String changeexpr, Object ns) { | |
398 | 0 | StateMachinesFactory sMFactory = Model.getStateMachinesFactory(); |
399 | 0 | if ((changeexpr == null) || ("".equals(changeexpr.trim()))) { |
400 | 0 | return sMFactory.buildChangeEvent(changeexpr, ns); |
401 | } | |
402 | 0 | Object result = null; |
403 | 0 | Object type = Model.getMetaTypes().getChangeEvent(); |
404 | 0 | Collection events = Model.getModelManagementHelper() |
405 | .getAllModelElementsOfKind(ns, type); | |
406 | 0 | for (Object event : events) { |
407 | 0 | Object expression = Model.getFacade().getExpression(event); |
408 | 0 | if (expression != null) { |
409 | 0 | if (changeexpr.equals(Model.getFacade().getBody(expression))) { |
410 | 0 | result = event; |
411 | 0 | break; |
412 | } | |
413 | } | |
414 | 0 | } |
415 | 0 | if (result == null) { |
416 | 0 | result = sMFactory.buildChangeEvent(changeexpr, ns); |
417 | } | |
418 | 0 | return result; |
419 | } | |
420 | ||
421 | protected Object findCallEvent(String callexpr, Object ns) { | |
422 | 0 | if ((callexpr == null) || ("".equals(callexpr.trim()))) { |
423 | 0 | return null; |
424 | } | |
425 | 0 | Object result = null; |
426 | 0 | Object type = Model.getMetaTypes().getCallEvent(); |
427 | 0 | Collection events = Model.getModelManagementHelper() |
428 | .getAllModelElementsOfKind(ns, type); | |
429 | 0 | for (Object event : events) { |
430 | 0 | if (callexpr.equals(Model.getFacade().getName(event))) { |
431 | /* Do not check if the parameters match. */ | |
432 | 0 | result = event; |
433 | 0 | break; |
434 | } | |
435 | } | |
436 | 0 | return result; |
437 | } | |
438 | ||
439 | /** | |
440 | * Handle the Guard of a Transition.<p> | |
441 | * | |
442 | * We can distinct between 4 cases:<ol> | |
443 | * <li>A guard is given. None exists yet. | |
444 | * <li>The expression of the guard was present, but is altered. | |
445 | * <li>A guard is not given. None exists yet. | |
446 | * <li>The expression of the guard was present, but is removed. | |
447 | * </ol> | |
448 | * | |
449 | * The reaction in these cases should be:<ol> | |
450 | * <li>Create a new guard, set its name, language & expression, | |
451 | * and hook it to the transition. | |
452 | * <li>Change the guard's expression. Leave the name & language | |
453 | * untouched. See also issue 2742. | |
454 | * <li>Nop. | |
455 | * <li>Unhook and erase the existing guard. | |
456 | * </ol> | |
457 | * | |
458 | * @param trans the UML element transition | |
459 | * @param guard the string that represents the guard expression | |
460 | */ | |
461 | private void parseGuard(Object trans, String guard) { | |
462 | 0 | Object g = Model.getFacade().getGuard(trans); |
463 | 0 | if (guard.length() > 0) { |
464 | 0 | if (g == null) { |
465 | // case 1 | |
466 | /*TODO: In the next line, I should use buildGuard(), | |
467 | * but it doesn't show the guard on the diagram... | |
468 | * Why? (MVW) | |
469 | */ | |
470 | 0 | g = Model.getStateMachinesFactory().createGuard(); |
471 | 0 | if (g != null) { |
472 | 0 | Model.getStateMachinesHelper().setExpression(g, |
473 | Model.getDataTypesFactory() | |
474 | .createBooleanExpression("", guard)); | |
475 | 0 | Model.getCoreHelper().setName(g, "anon"); |
476 | 0 | Model.getCommonBehaviorHelper().setTransition(g, trans); |
477 | ||
478 | // NSUML does this (?) | |
479 | // Model.getFacade().setGuard(trans, g); | |
480 | } | |
481 | } else { | |
482 | // case 2 | |
483 | 0 | Object expr = Model.getFacade().getExpression(g); |
484 | 0 | String language = ""; |
485 | ||
486 | /* TODO: This does not work! (MVW) | |
487 | Model.getFacade().setBody(expr,guard); | |
488 | Model.getFacade().setExpression(g,expr); */ | |
489 | ||
490 | //hence a less elegant workaround that works: | |
491 | 0 | if (expr != null) { |
492 | 0 | language = Model.getDataTypesHelper().getLanguage(expr); |
493 | } | |
494 | 0 | Model.getStateMachinesHelper().setExpression(g, |
495 | Model.getDataTypesFactory() | |
496 | .createBooleanExpression(language, guard)); | |
497 | /* TODO: In this case, the properties panel | |
498 | is not updated with the changed expression! */ | |
499 | 0 | } |
500 | } else { | |
501 | 0 | if (g == null) { |
502 | /* case 3 */ | |
503 | } else { | |
504 | // case 4 | |
505 | 0 | delete(g); // erase it |
506 | } | |
507 | } | |
508 | 0 | } |
509 | ||
510 | /** | |
511 | * Handle the Effect (Action) of a Transition.<p> | |
512 | * | |
513 | * We can distinct between 4 cases:<ul> | |
514 | * <li>1. An effect is given. None exists yet. | |
515 | * <li>2. The expression of the effect was present, but is altered. | |
516 | * <li>3. An effect is not given. None exists yet. | |
517 | * <li>4. The expression of the effect was present, but is removed. | |
518 | * </ul> | |
519 | * | |
520 | * The reaction in these cases should be:<ul> | |
521 | * <li>1. Create a new CallAction, set its name, language & | |
522 | * expression, and hook it to the transition. | |
523 | * <li>2. Change the effect's expression. Leave the actiontype, name | |
524 | * & language untouched. | |
525 | * <li>3. Nop. | |
526 | * <li>4. Unhook and erase the existing effect. | |
527 | * </ul> | |
528 | * | |
529 | * @param actions the string to be parsed | |
530 | * @param trans the transition that causes the effect (actions) | |
531 | */ | |
532 | private void parseEffect(Object trans, String actions) { | |
533 | 0 | Object effect = Model.getFacade().getEffect(trans); |
534 | 0 | if (actions.length() > 0) { |
535 | 0 | if (effect == null) { // case 1 |
536 | 0 | effect = |
537 | Model.getCommonBehaviorFactory() | |
538 | .createCallAction(); | |
539 | /* And hook it to the transition immediately, | |
540 | * so that an exception can not cause it to remain dangling: */ | |
541 | 0 | Model.getStateMachinesHelper().setEffect(trans, effect); |
542 | 0 | Model.getCommonBehaviorHelper().setScript(effect, |
543 | Model.getDataTypesFactory() | |
544 | .createActionExpression(""/*language*/, | |
545 | actions)); | |
546 | 0 | Model.getCoreHelper().setName(effect, "anon"); |
547 | } else { // case 2 | |
548 | 0 | Object script = Model.getFacade().getScript(effect); |
549 | 0 | String language = (script == null) ? null |
550 | : Model.getDataTypesHelper().getLanguage(script); | |
551 | 0 | Model.getCommonBehaviorHelper().setScript(effect, |
552 | Model.getDataTypesFactory() | |
553 | .createActionExpression(language, actions)); | |
554 | 0 | } |
555 | } else { // case 3 & 4 | |
556 | 0 | if (effect == null) { |
557 | // case 3 | |
558 | } else { | |
559 | // case 4 | |
560 | 0 | delete(effect); // erase it |
561 | } | |
562 | } | |
563 | 0 | } |
564 | ||
565 | /** | |
566 | * This deletes modelelements, and swallows null without barking. | |
567 | * | |
568 | * @author Michiel van der Wulp | |
569 | * @param obj | |
570 | * the modelelement to be deleted | |
571 | */ | |
572 | private void delete(Object obj) { | |
573 | 0 | if (obj != null) { |
574 | 0 | Model.getUmlFactory().delete(obj); |
575 | } | |
576 | 0 | } |
577 | ||
578 | /* | |
579 | * @see org.argouml.uml.notation.NotationProvider#getParsingHelp() | |
580 | */ | |
581 | public String getParsingHelp() { | |
582 | 0 | return "parsing.help.fig-transition"; |
583 | } | |
584 | ||
585 | @Override | |
586 | public String toString(Object modelElement, NotationSettings settings) { | |
587 | 0 | return toString(modelElement); |
588 | } | |
589 | ||
590 | private String toString(Object modelElement) { | |
591 | 0 | Object trigger = Model.getFacade().getTrigger(modelElement); |
592 | 0 | Object guard = Model.getFacade().getGuard(modelElement); |
593 | 0 | Object effect = Model.getFacade().getEffect(modelElement); |
594 | 0 | String t = generateEvent(trigger); |
595 | 0 | String g = generateGuard(guard); |
596 | 0 | String e = NotationUtilityUml.generateActionSequence(effect); |
597 | 0 | if (g.length() > 0) { |
598 | 0 | t += " [" + g + "]"; |
599 | } | |
600 | 0 | if (e.length() > 0) { |
601 | 0 | t += " / " + e; |
602 | } | |
603 | 0 | return t; |
604 | } | |
605 | ||
606 | /** | |
607 | * Generates the text for a (trigger) event. | |
608 | * | |
609 | * @param m Object of any MEvent kind | |
610 | * @return the string representing the event | |
611 | */ | |
612 | private String generateEvent(Object m) { | |
613 | 0 | if (m == null) { |
614 | 0 | return ""; |
615 | } | |
616 | 0 | StringBuffer event = new StringBuffer(); |
617 | 0 | if (Model.getFacade().isAChangeEvent(m)) { |
618 | 0 | event.append("when("); |
619 | 0 | event.append( |
620 | generateExpression(Model.getFacade().getExpression(m))); | |
621 | 0 | event.append(")"); |
622 | 0 | } else if (Model.getFacade().isATimeEvent(m)) { |
623 | 0 | event.append("after("); |
624 | 0 | event.append( |
625 | generateExpression(Model.getFacade().getExpression(m))); | |
626 | 0 | event.append(")"); |
627 | 0 | } else if (Model.getFacade().isASignalEvent(m)) { |
628 | 0 | event.append(Model.getFacade().getName(m)); |
629 | 0 | } else if (Model.getFacade().isACallEvent(m)) { |
630 | 0 | event.append(Model.getFacade().getName(m)); |
631 | 0 | event.append(generateParameterList(m)); |
632 | } | |
633 | 0 | return event.toString(); |
634 | } | |
635 | ||
636 | /** | |
637 | * Generates a string representing the given Guard. <p> | |
638 | * | |
639 | * If there is an expression, then its body text is returned. | |
640 | * Else, a 0 length string is returned. <p> | |
641 | * | |
642 | * Apparently, the AndroMDA people are convinced that the | |
643 | * name of the guard should be shown on the diagram, while this | |
644 | * is not correct according the UML standard. | |
645 | * ArgoUML does not support this feature because of its down-side: | |
646 | * The name would end up in the expression | |
647 | * if you edit the transition text on the diagram, | |
648 | * while the name remains containing the old value. | |
649 | * | |
650 | * @param m the UML Guard object | |
651 | * @return a string | |
652 | */ | |
653 | private String generateGuard(Object m) { | |
654 | 0 | if (m != null) { |
655 | 0 | if (Model.getFacade().getExpression(m) != null) { |
656 | 0 | return generateExpression(Model.getFacade().getExpression(m)); |
657 | } | |
658 | } | |
659 | 0 | return ""; |
660 | } | |
661 | ||
662 | /** | |
663 | * Generates a list of parameters. The parameters belong to the | |
664 | * given object. The returned string will have the following | |
665 | * syntax:<p> | |
666 | * | |
667 | * (param1, param2, param3, ..., paramN)<p> | |
668 | * | |
669 | * If there are no parameters, then "()" is returned. | |
670 | * | |
671 | * @param parameterListOwner the 'owner' of the parameters | |
672 | * @return the generated parameter list | |
673 | */ | |
674 | private String generateParameterList(Object parameterListOwner) { | |
675 | 0 | Iterator it = |
676 | Model.getFacade().getParameters(parameterListOwner).iterator(); | |
677 | 0 | StringBuffer list = new StringBuffer(); |
678 | 0 | list.append("("); |
679 | 0 | if (it.hasNext()) { |
680 | 0 | while (it.hasNext()) { |
681 | 0 | Object param = it.next(); |
682 | 0 | list.append(generateParameter(param)); |
683 | 0 | if (it.hasNext()) { |
684 | 0 | list.append(", "); |
685 | } | |
686 | 0 | } |
687 | } | |
688 | 0 | list.append(")"); |
689 | 0 | return list.toString(); |
690 | } | |
691 | ||
692 | private String generateExpression(Object expr) { | |
693 | 0 | if (Model.getFacade().isAExpression(expr)) { |
694 | 0 | Object body = Model.getFacade().getBody(expr); |
695 | 0 | if (body != null) { |
696 | 0 | return (String) body; |
697 | } | |
698 | } | |
699 | 0 | return ""; |
700 | } | |
701 | ||
702 | /** | |
703 | * Generates the representation of a parameter on the display | |
704 | * (diagram). The string to be returned will have the following | |
705 | * syntax:<p> | |
706 | * | |
707 | * kind name : type-expression = default-value | |
708 | * | |
709 | * @param parameter the parameter | |
710 | * @return the generated text | |
711 | */ | |
712 | public String generateParameter(Object parameter) { | |
713 | 0 | StringBuffer s = new StringBuffer(); |
714 | 0 | s.append(generateKind(Model.getFacade().getKind(parameter))); |
715 | 0 | if (s.length() > 0) { |
716 | 0 | s.append(" "); |
717 | } | |
718 | 0 | s.append(Model.getFacade().getName(parameter)); |
719 | 0 | String classRef = |
720 | generateClassifierRef(Model.getFacade().getType(parameter)); | |
721 | 0 | if (classRef.length() > 0) { |
722 | 0 | s.append(" : "); |
723 | 0 | s.append(classRef); |
724 | } | |
725 | 0 | String defaultValue = |
726 | generateExpression(Model.getFacade().getDefaultValue(parameter)); | |
727 | 0 | if (defaultValue.length() > 0) { |
728 | 0 | s.append(" = "); |
729 | 0 | s.append(defaultValue); |
730 | } | |
731 | 0 | return s.toString(); |
732 | } | |
733 | ||
734 | private String generateKind(Object /*Parameter etc.*/ kind) { | |
735 | 0 | StringBuffer s = new StringBuffer(); |
736 | 0 | if (kind == null /* "in" is the default */ |
737 | || kind == Model.getDirectionKind().getInParameter()) { | |
738 | 0 | s.append(/*"in"*/ ""); /* See issue 3421. */ |
739 | 0 | } else if (kind == Model.getDirectionKind().getInOutParameter()) { |
740 | 0 | s.append("inout"); |
741 | 0 | } else if (kind == Model.getDirectionKind().getReturnParameter()) { |
742 | // return nothing | |
743 | 0 | } else if (kind == Model.getDirectionKind().getOutParameter()) { |
744 | 0 | s.append("out"); |
745 | } | |
746 | 0 | return s.toString(); |
747 | } | |
748 | ||
749 | /** | |
750 | * Generate the type of a parameter, i.e. a reference to a classifier. | |
751 | * | |
752 | * @param cls the classifier | |
753 | * @return the generated text | |
754 | */ | |
755 | private String generateClassifierRef(Object cls) { | |
756 | 0 | if (cls == null) { |
757 | 0 | return ""; |
758 | } | |
759 | 0 | return Model.getFacade().getName(cls); |
760 | } | |
761 | ||
762 | } |