Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
StateBodyNotationUml |
|
| 2.761904761904762;2.762 | ||||
StateBodyNotationUml$ModelElementInfoList |
|
| 2.761904761904762;2.762 | ||||
StateBodyNotationUml$ModelElementInfoList$InfoItem |
|
| 2.761904761904762;2.762 |
1 | /* $Id: StateBodyNotationUml.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.ArrayList; | |
43 | import java.util.Collection; | |
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.notation.NotationSettings; | |
52 | import org.argouml.notation.providers.StateBodyNotation; | |
53 | ||
54 | /** | |
55 | * UML notation for the body of a state. | |
56 | * | |
57 | * @author Michiel van der Wulp | |
58 | */ | |
59 | 0 | public class StateBodyNotationUml extends StateBodyNotation { |
60 | ||
61 | /** | |
62 | * The default language for an expression. | |
63 | */ | |
64 | private static final String LANGUAGE = "Java"; | |
65 | ||
66 | /** | |
67 | * The constructor. | |
68 | * | |
69 | * @param state the state represented by the notation | |
70 | */ | |
71 | public StateBodyNotationUml(Object state) { | |
72 | 0 | super(state); |
73 | 0 | } |
74 | ||
75 | /* | |
76 | * @see org.argouml.uml.notation.NotationProvider#parse(java.lang.Object, java.lang.String) | |
77 | */ | |
78 | public void parse(Object modelElement, String text) { | |
79 | try { | |
80 | 0 | parseStateBody(modelElement, text); |
81 | 0 | } catch (ParseException pe) { |
82 | 0 | String msg = "statusmsg.bar.error.parsing.statebody"; |
83 | 0 | Object[] args = { |
84 | pe.getLocalizedMessage(), | |
85 | Integer.valueOf(pe.getErrorOffset()), | |
86 | }; | |
87 | 0 | ArgoEventPump.fireEvent(new ArgoHelpEvent( |
88 | ArgoEventTypes.HELP_CHANGED, this, | |
89 | Translator.messageFormat(msg, args))); | |
90 | 0 | } |
91 | 0 | } |
92 | ||
93 | /* | |
94 | * @see org.argouml.uml.notation.NotationProvider#getParsingHelp() | |
95 | */ | |
96 | public String getParsingHelp() { | |
97 | 0 | return "parsing.help.fig-statebody"; |
98 | } | |
99 | ||
100 | @Override | |
101 | public String toString(Object modelElement, NotationSettings settings) { | |
102 | ||
103 | 0 | StringBuffer s = new StringBuffer(); |
104 | ||
105 | 0 | Object entryAction = Model.getFacade().getEntry(modelElement); |
106 | 0 | Object exitAction = Model.getFacade().getExit(modelElement); |
107 | 0 | Object doAction = Model.getFacade().getDoActivity(modelElement); |
108 | 0 | if (entryAction != null) { |
109 | 0 | String entryStr = |
110 | NotationUtilityUml.generateActionSequence(entryAction); | |
111 | 0 | s.append("entry /").append(entryStr); |
112 | } | |
113 | 0 | if (doAction != null) { |
114 | 0 | String doStr = NotationUtilityUml.generateActionSequence(doAction); |
115 | 0 | if (s.length() > 0) { |
116 | 0 | s.append("\n"); |
117 | } | |
118 | 0 | s.append("do /").append(doStr); |
119 | ||
120 | } | |
121 | 0 | if (exitAction != null) { |
122 | 0 | String exitStr = |
123 | NotationUtilityUml.generateActionSequence(exitAction); | |
124 | 0 | if (s.length() > 0) { |
125 | 0 | s.append("\n"); |
126 | } | |
127 | 0 | s.append("exit /").append(exitStr); |
128 | } | |
129 | 0 | Collection internaltrans = |
130 | Model.getFacade().getInternalTransitions(modelElement); | |
131 | 0 | if (internaltrans != null) { |
132 | 0 | for (Object trans : internaltrans) { |
133 | 0 | if (s.length() > 0) { |
134 | 0 | s.append("\n"); |
135 | } | |
136 | /* TODO: Is this a good way of handling nested notation? */ | |
137 | 0 | s.append((new TransitionNotationUml(trans)).toString(trans, |
138 | settings)); | |
139 | } | |
140 | } | |
141 | 0 | return s.toString(); |
142 | } | |
143 | ||
144 | /** | |
145 | * Parse user input for state bodies and assign the individual lines to | |
146 | * according actions or transitions. The user input consists of multiple | |
147 | * lines like:<pre> | |
148 | * action-label / action-expression | |
149 | * </pre> or the format of a regular | |
150 | * transition - see parseTransition(). <p> | |
151 | * | |
152 | * "action-label" stands for one of "entry", "do" and "exit". | |
153 | * The words "entry", "do" and "exit" are case-independent. | |
154 | * | |
155 | * @param st The State object. | |
156 | * @param s The string to parse. | |
157 | * @throws ParseException when there is a syntax problem, | |
158 | * e.g. non-matching brackets () or [] | |
159 | */ | |
160 | protected void parseStateBody(Object st, String s) throws ParseException { | |
161 | 0 | boolean foundEntry = false; |
162 | 0 | boolean foundExit = false; |
163 | 0 | boolean foundDo = false; |
164 | ||
165 | /* Generate all the existing internal transitions, | |
166 | * so that we can compare them as text with the newly entered ones. | |
167 | */ | |
168 | 0 | ModelElementInfoList internalsInfo = |
169 | new ModelElementInfoList( | |
170 | Model.getFacade().getInternalTransitions(st)); | |
171 | ||
172 | 0 | StringTokenizer lines = new StringTokenizer(s, "\n\r"); |
173 | 0 | while (lines.hasMoreTokens()) { |
174 | 0 | String line = lines.nextToken().trim(); |
175 | /* Now let's check if the new line is already present in | |
176 | * the old list of internal transitions; if it is, then | |
177 | * mark the old one to be retained (i.e. do not create a new one), | |
178 | * if it isn't, continue with parsing: | |
179 | */ | |
180 | 0 | if (!internalsInfo.checkRetain(line)) { |
181 | 0 | if (line.toLowerCase().startsWith("entry") |
182 | && line.substring(5).trim().startsWith("/")) { | |
183 | 0 | parseStateEntryAction(st, line); |
184 | 0 | foundEntry = true; |
185 | 0 | } else if (line.toLowerCase().startsWith("exit") |
186 | && line.substring(4).trim().startsWith("/")) { | |
187 | 0 | parseStateExitAction(st, line); |
188 | 0 | foundExit = true; |
189 | 0 | } else if (line.toLowerCase().startsWith("do") |
190 | && line.substring(2).trim().startsWith("/")) { | |
191 | 0 | parseStateDoAction(st, line); |
192 | 0 | foundDo = true; |
193 | } else { | |
194 | 0 | Object t = |
195 | Model.getStateMachinesFactory() | |
196 | .buildInternalTransition(st); | |
197 | 0 | if (t == null) { |
198 | 0 | continue; |
199 | } | |
200 | /* TODO: If the next line trows an exception, then what | |
201 | * do we do with the remainder of the | |
202 | * parsed/to be parsed lines? | |
203 | */ | |
204 | /* TODO: Is this a good way of handling nested notation? | |
205 | * The following fails the tests: | |
206 | * new TransitionNotationUml(t).parse(line); | |
207 | */ | |
208 | 0 | new TransitionNotationUml(t).parseTransition(t, line); |
209 | /* Add this new one, and mark it to be retained: */ | |
210 | 0 | internalsInfo.add(t, true); |
211 | } | |
212 | } | |
213 | 0 | } |
214 | ||
215 | 0 | if (!foundEntry) { |
216 | 0 | delete(Model.getFacade().getEntry(st)); |
217 | } | |
218 | 0 | if (!foundExit) { |
219 | 0 | delete(Model.getFacade().getExit(st)); |
220 | } | |
221 | 0 | if (!foundDo) { |
222 | 0 | delete(Model.getFacade().getDoActivity(st)); |
223 | } | |
224 | ||
225 | /* Process the final list of internal transitions, | |
226 | * and hook it to the state: | |
227 | */ | |
228 | 0 | Model.getStateMachinesHelper().setInternalTransitions(st, |
229 | internalsInfo.finalisedList()); | |
230 | 0 | } |
231 | ||
232 | /** | |
233 | * This class manages a list of UML modelelements that existed | |
234 | * before and after the parseXxxxx() function was called. | |
235 | * It has all the knowledge to deal with additions and removals. | |
236 | * | |
237 | * @author MVW | |
238 | */ | |
239 | class ModelElementInfoList { | |
240 | /** | |
241 | * The list that we maintain. | |
242 | */ | |
243 | private Collection<InfoItem> theList; | |
244 | ||
245 | /** | |
246 | * An item in a list, maintains all info about one UML object, | |
247 | * its generated version (i.e. textual representation), | |
248 | * and if it needs to be retained after parsing.<p> | |
249 | * | |
250 | * @author MVW | |
251 | */ | |
252 | class InfoItem { | |
253 | private TransitionNotationUml generator; | |
254 | private Object umlObject; | |
255 | private boolean retainIt; | |
256 | ||
257 | /** | |
258 | * The constructor. | |
259 | * @param obj the UML object | |
260 | */ | |
261 | 0 | InfoItem(Object obj) { |
262 | 0 | umlObject = obj; |
263 | 0 | generator = new TransitionNotationUml(obj); |
264 | 0 | } |
265 | ||
266 | /** | |
267 | * The constructor. | |
268 | * | |
269 | * @param obj the UML object. | |
270 | * @param r | |
271 | */ | |
272 | InfoItem(Object obj, boolean r) { | |
273 | 0 | this(obj); |
274 | 0 | retainIt = r; |
275 | 0 | } |
276 | ||
277 | /** | |
278 | * @return the generated string representation | |
279 | */ | |
280 | String getGenerated() { | |
281 | 0 | return generator.toString(); |
282 | } | |
283 | ||
284 | /** | |
285 | * @return the UML Object | |
286 | */ | |
287 | Object getUmlObject() { | |
288 | 0 | return umlObject; |
289 | } | |
290 | ||
291 | /** | |
292 | * Retain this UML object. | |
293 | */ | |
294 | void retain() { | |
295 | 0 | retainIt = true; |
296 | 0 | } |
297 | ||
298 | /** | |
299 | * @return true if the UML object is to be retained, | |
300 | * false if it is to be deleted | |
301 | */ | |
302 | boolean isRetained() { | |
303 | 0 | return retainIt; |
304 | } | |
305 | } | |
306 | ||
307 | /** | |
308 | * The constructor. | |
309 | * | |
310 | * @param c the collection of the UML objects | |
311 | * that were present before | |
312 | */ | |
313 | 0 | ModelElementInfoList(Collection c) { |
314 | 0 | theList = new ArrayList<InfoItem>(); |
315 | 0 | for (Object obj : c) { |
316 | 0 | theList.add(new InfoItem(obj)); |
317 | } | |
318 | 0 | } |
319 | ||
320 | /** | |
321 | * @param obj the UML object | |
322 | * @param r true if this UML object needs to be retained | |
323 | */ | |
324 | void add(Object obj, boolean r) { | |
325 | 0 | theList.add(new InfoItem(obj, r)); |
326 | 0 | } |
327 | ||
328 | /** | |
329 | * Check the given textual description, | |
330 | * and if already present in the list, then retain it. | |
331 | * @param line the given textual description | |
332 | * @return true if the item was already present in the list | |
333 | */ | |
334 | boolean checkRetain(String line) { | |
335 | 0 | for (InfoItem tInfo : theList) { |
336 | 0 | if (tInfo.getGenerated().equals(line)) { |
337 | 0 | tInfo.retain(); |
338 | 0 | return true; |
339 | } | |
340 | } | |
341 | 0 | return false; |
342 | } | |
343 | ||
344 | /** | |
345 | * Finish the procedure, by deleting the UML model items | |
346 | * that are not to be retained, and return a collection | |
347 | * of the ones to be retained. | |
348 | * This method should only be called once! | |
349 | * @return the UML objects that survive. | |
350 | */ | |
351 | Collection finalisedList() { | |
352 | // don't forget to remove old internals! | |
353 | 0 | Collection<Object> newModelElementsList = new ArrayList<Object>(); |
354 | 0 | for (InfoItem tInfo : theList) { |
355 | 0 | if (tInfo.isRetained()) { |
356 | 0 | newModelElementsList.add(tInfo.getUmlObject()); |
357 | } else { | |
358 | 0 | delete(tInfo.getUmlObject()); |
359 | } | |
360 | } | |
361 | // Make next accesses to this instance predictable: | |
362 | 0 | theList.clear(); |
363 | // and hook in the new ones: | |
364 | 0 | return newModelElementsList; |
365 | } | |
366 | } | |
367 | ||
368 | ||
369 | /** | |
370 | * Parse a line of the form: "entry /action" and create an action. | |
371 | * We do not need to check for the presence of the word "entry" - that | |
372 | * is done by the caller. | |
373 | * | |
374 | * @param st the state object | |
375 | * @param s the string to be parsed | |
376 | */ | |
377 | private void parseStateEntryAction(Object st, String s) { | |
378 | 0 | if (s.indexOf("/") > -1) { |
379 | 0 | s = s.substring(s.indexOf("/") + 1).trim(); |
380 | } | |
381 | 0 | Object oldEntry = Model.getFacade().getEntry(st); |
382 | 0 | if (oldEntry == null) { |
383 | 0 | Model.getStateMachinesHelper().setEntry(st, buildNewCallAction(s)); |
384 | } else { | |
385 | 0 | updateAction(oldEntry, s); |
386 | } | |
387 | 0 | } |
388 | ||
389 | /** | |
390 | * Parse a line of the form: "exit /action" and create an action. | |
391 | * We do not need to check for the presence of the word "exit" - that | |
392 | * is done by the caller. | |
393 | * | |
394 | * @param st | |
395 | * the state object | |
396 | * @param s | |
397 | * the string to be parsed | |
398 | */ | |
399 | private void parseStateExitAction(Object st, String s) { | |
400 | 0 | if (s.indexOf("/") > -1) { |
401 | 0 | s = s.substring(s.indexOf("/") + 1).trim(); |
402 | } | |
403 | 0 | Object oldExit = Model.getFacade().getExit(st); |
404 | 0 | if (oldExit == null) { |
405 | 0 | Model.getStateMachinesHelper().setExit(st, buildNewCallAction(s)); |
406 | } else { | |
407 | 0 | updateAction(oldExit, s); |
408 | } | |
409 | 0 | } |
410 | ||
411 | /** | |
412 | * Parse a line of the form: "do /action" and create an action. | |
413 | * We do not need to check for the presence of the word "do" - that | |
414 | * is done by the caller. | |
415 | * | |
416 | * @param st the state object | |
417 | * @param s the string to be parsed | |
418 | */ | |
419 | private void parseStateDoAction(Object st, String s) { | |
420 | 0 | if (s.indexOf("/") > -1) { |
421 | 0 | s = s.substring(s.indexOf("/") + 1).trim(); |
422 | } | |
423 | 0 | Object oldDo = Model.getFacade().getDoActivity(st); |
424 | 0 | if (oldDo == null) { |
425 | 0 | Model.getStateMachinesHelper().setDoActivity(st, |
426 | buildNewCallAction(s)); | |
427 | } else { | |
428 | 0 | updateAction(oldDo, s); |
429 | } | |
430 | 0 | } |
431 | ||
432 | /** | |
433 | * This builds a CallAction with default attributes. But without Operation! | |
434 | * | |
435 | * @author MVW | |
436 | * @param s | |
437 | * string representing the Script of the Action | |
438 | * @return The newly created CallAction. | |
439 | */ | |
440 | private Object buildNewCallAction(String s) { | |
441 | 0 | Object a = |
442 | Model.getCommonBehaviorFactory().createCallAction(); | |
443 | 0 | Object ae = |
444 | Model.getDataTypesFactory().createActionExpression(LANGUAGE, s); | |
445 | 0 | Model.getCommonBehaviorHelper().setScript(a, ae); |
446 | 0 | Model.getCoreHelper().setName(a, "anon"); |
447 | 0 | return a; |
448 | } | |
449 | ||
450 | /** | |
451 | * Update an existing Action with a new Script. | |
452 | * | |
453 | * @author MVW | |
454 | * @param old the Action | |
455 | * @param s a string representing a new Script for the ActionExpression | |
456 | */ | |
457 | private void updateAction(Object old, String s) { | |
458 | 0 | Object ae = Model.getFacade().getScript(old); // the ActionExpression |
459 | 0 | String language = LANGUAGE; |
460 | 0 | if (ae != null) { |
461 | 0 | language = Model.getDataTypesHelper().getLanguage(ae); |
462 | 0 | String body = (String) Model.getFacade().getBody(ae); |
463 | 0 | if (body.equals(s)) { |
464 | 0 | return; |
465 | } | |
466 | } | |
467 | 0 | ae = Model.getDataTypesFactory().createActionExpression(language, s); |
468 | 0 | Model.getCommonBehaviorHelper().setScript(old, ae); |
469 | 0 | } |
470 | ||
471 | /** | |
472 | * This deletes modelelements, and swallows null without barking. | |
473 | * | |
474 | * @author Michiel | |
475 | * @param obj | |
476 | * the modelelement to be deleted | |
477 | */ | |
478 | private void delete(Object obj) { | |
479 | 0 | if (obj != null) { |
480 | 0 | Model.getUmlFactory().delete(obj); |
481 | } | |
482 | 0 | } |
483 | ||
484 | } |