/* $Id: StateBodyNotationUml.java 18852 2010-11-20 19:27:11Z mvw $
*****************************************************************************
* Copyright (c) 2009-2010 Contributors - see below
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Michiel van der Wulp
*****************************************************************************
*
* Some portions of this file was previously release using the BSD License:
*/
// Copyright (c) 2005-2009 The Regents of the University of California. All
// Rights Reserved. Permission to use, copy, modify, and distribute this
// software and its documentation without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph appear in all copies. This software program and
// documentation are copyrighted by The Regents of the University of
// California. The software program and documentation are supplied "AS
// IS", without any accompanying services from The Regents. The Regents
// does not warrant that the operation of the program will be
// uninterrupted or error-free. The end-user understands that the program
// was developed for research purposes and is advised not to rely
// exclusively on the program for any reason. IN NO EVENT SHALL THE
// UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
// SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
// UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
package org.argouml.notation.providers.uml;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.StringTokenizer;
import org.argouml.application.events.ArgoEventPump;
import org.argouml.application.events.ArgoEventTypes;
import org.argouml.application.events.ArgoHelpEvent;
import org.argouml.i18n.Translator;
import org.argouml.model.Model;
import org.argouml.notation.NotationSettings;
import org.argouml.notation.providers.StateBodyNotation;
/**
* UML notation for the body of a state.
*
* @author Michiel van der Wulp
*/
public class StateBodyNotationUml extends StateBodyNotation {
/**
* The default language for an expression.
*/
private static final String LANGUAGE = "Java";
/**
* The constructor.
*
* @param state the state represented by the notation
*/
public StateBodyNotationUml(Object state) {
super(state);
}
/*
* @see org.argouml.uml.notation.NotationProvider#parse(java.lang.Object, java.lang.String)
*/
public void parse(Object modelElement, String text) {
try {
parseStateBody(modelElement, text);
} catch (ParseException pe) {
String msg = "statusmsg.bar.error.parsing.statebody";
Object[] args = {
pe.getLocalizedMessage(),
Integer.valueOf(pe.getErrorOffset()),
};
ArgoEventPump.fireEvent(new ArgoHelpEvent(
ArgoEventTypes.HELP_CHANGED, this,
Translator.messageFormat(msg, args)));
}
}
/*
* @see org.argouml.uml.notation.NotationProvider#getParsingHelp()
*/
public String getParsingHelp() {
return "parsing.help.fig-statebody";
}
@Override
public String toString(Object modelElement, NotationSettings settings) {
StringBuffer s = new StringBuffer();
Object entryAction = Model.getFacade().getEntry(modelElement);
Object exitAction = Model.getFacade().getExit(modelElement);
Object doAction = Model.getFacade().getDoActivity(modelElement);
if (entryAction != null) {
String entryStr =
NotationUtilityUml.generateActionSequence(entryAction);
s.append("entry /").append(entryStr);
}
if (doAction != null) {
String doStr = NotationUtilityUml.generateActionSequence(doAction);
if (s.length() > 0) {
s.append("\n");
}
s.append("do /").append(doStr);
}
if (exitAction != null) {
String exitStr =
NotationUtilityUml.generateActionSequence(exitAction);
if (s.length() > 0) {
s.append("\n");
}
s.append("exit /").append(exitStr);
}
Collection internaltrans =
Model.getFacade().getInternalTransitions(modelElement);
if (internaltrans != null) {
for (Object trans : internaltrans) {
if (s.length() > 0) {
s.append("\n");
}
/* TODO: Is this a good way of handling nested notation? */
s.append((new TransitionNotationUml(trans)).toString(trans,
settings));
}
}
return s.toString();
}
/**
* Parse user input for state bodies and assign the individual lines to
* according actions or transitions. The user input consists of multiple
* lines like:<pre>
* action-label / action-expression
* </pre> or the format of a regular
* transition - see parseTransition(). <p>
*
* "action-label" stands for one of "entry", "do" and "exit".
* The words "entry", "do" and "exit" are case-independent.
*
* @param st The State object.
* @param s The string to parse.
* @throws ParseException when there is a syntax problem,
* e.g. non-matching brackets () or []
*/
protected void parseStateBody(Object st, String s) throws ParseException {
boolean foundEntry = false;
boolean foundExit = false;
boolean foundDo = false;
/* Generate all the existing internal transitions,
* so that we can compare them as text with the newly entered ones.
*/
ModelElementInfoList internalsInfo =
new ModelElementInfoList(
Model.getFacade().getInternalTransitions(st));
StringTokenizer lines = new StringTokenizer(s, "\n\r");
while (lines.hasMoreTokens()) {
String line = lines.nextToken().trim();
/* Now let's check if the new line is already present in
* the old list of internal transitions; if it is, then
* mark the old one to be retained (i.e. do not create a new one),
* if it isn't, continue with parsing:
*/
if (!internalsInfo.checkRetain(line)) {
if (line.toLowerCase().startsWith("entry")
&& line.substring(5).trim().startsWith("/")) {
parseStateEntryAction(st, line);
foundEntry = true;
} else if (line.toLowerCase().startsWith("exit")
&& line.substring(4).trim().startsWith("/")) {
parseStateExitAction(st, line);
foundExit = true;
} else if (line.toLowerCase().startsWith("do")
&& line.substring(2).trim().startsWith("/")) {
parseStateDoAction(st, line);
foundDo = true;
} else {
Object t =
Model.getStateMachinesFactory()
.buildInternalTransition(st);
if (t == null) {
continue;
}
/* TODO: If the next line trows an exception, then what
* do we do with the remainder of the
* parsed/to be parsed lines?
*/
/* TODO: Is this a good way of handling nested notation?
* The following fails the tests:
* new TransitionNotationUml(t).parse(line);
*/
new TransitionNotationUml(t).parseTransition(t, line);
/* Add this new one, and mark it to be retained: */
internalsInfo.add(t, true);
}
}
}
if (!foundEntry) {
delete(Model.getFacade().getEntry(st));
}
if (!foundExit) {
delete(Model.getFacade().getExit(st));
}
if (!foundDo) {
delete(Model.getFacade().getDoActivity(st));
}
/* Process the final list of internal transitions,
* and hook it to the state:
*/
Model.getStateMachinesHelper().setInternalTransitions(st,
internalsInfo.finalisedList());
}
/**
* This class manages a list of UML modelelements that existed
* before and after the parseXxxxx() function was called.
* It has all the knowledge to deal with additions and removals.
*
* @author MVW
*/
class ModelElementInfoList {
/**
* The list that we maintain.
*/
private Collection<InfoItem> theList;
/**
* An item in a list, maintains all info about one UML object,
* its generated version (i.e. textual representation),
* and if it needs to be retained after parsing.<p>
*
* @author MVW
*/
class InfoItem {
private TransitionNotationUml generator;
private Object umlObject;
private boolean retainIt;
/**
* The constructor.
* @param obj the UML object
*/
InfoItem(Object obj) {
umlObject = obj;
generator = new TransitionNotationUml(obj);
}
/**
* The constructor.
*
* @param obj the UML object.
* @param r
*/
InfoItem(Object obj, boolean r) {
this(obj);
retainIt = r;
}
/**
* @return the generated string representation
*/
String getGenerated() {
return generator.toString();
}
/**
* @return the UML Object
*/
Object getUmlObject() {
return umlObject;
}
/**
* Retain this UML object.
*/
void retain() {
retainIt = true;
}
/**
* @return true if the UML object is to be retained,
* false if it is to be deleted
*/
boolean isRetained() {
return retainIt;
}
}
/**
* The constructor.
*
* @param c the collection of the UML objects
* that were present before
*/
ModelElementInfoList(Collection c) {
theList = new ArrayList<InfoItem>();
for (Object obj : c) {
theList.add(new InfoItem(obj));
}
}
/**
* @param obj the UML object
* @param r true if this UML object needs to be retained
*/
void add(Object obj, boolean r) {
theList.add(new InfoItem(obj, r));
}
/**
* Check the given textual description,
* and if already present in the list, then retain it.
* @param line the given textual description
* @return true if the item was already present in the list
*/
boolean checkRetain(String line) {
for (InfoItem tInfo : theList) {
if (tInfo.getGenerated().equals(line)) {
tInfo.retain();
return true;
}
}
return false;
}
/**
* Finish the procedure, by deleting the UML model items
* that are not to be retained, and return a collection
* of the ones to be retained.
* This method should only be called once!
* @return the UML objects that survive.
*/
Collection finalisedList() {
// don't forget to remove old internals!
Collection<Object> newModelElementsList = new ArrayList<Object>();
for (InfoItem tInfo : theList) {
if (tInfo.isRetained()) {
newModelElementsList.add(tInfo.getUmlObject());
} else {
delete(tInfo.getUmlObject());
}
}
// Make next accesses to this instance predictable:
theList.clear();
// and hook in the new ones:
return newModelElementsList;
}
}
/**
* Parse a line of the form: "entry /action" and create an action.
* We do not need to check for the presence of the word "entry" - that
* is done by the caller.
*
* @param st the state object
* @param s the string to be parsed
*/
private void parseStateEntryAction(Object st, String s) {
if (s.indexOf("/") > -1) {
s = s.substring(s.indexOf("/") + 1).trim();
}
Object oldEntry = Model.getFacade().getEntry(st);
if (oldEntry == null) {
Model.getStateMachinesHelper().setEntry(st, buildNewCallAction(s));
} else {
updateAction(oldEntry, s);
}
}
/**
* Parse a line of the form: "exit /action" and create an action.
* We do not need to check for the presence of the word "exit" - that
* is done by the caller.
*
* @param st
* the state object
* @param s
* the string to be parsed
*/
private void parseStateExitAction(Object st, String s) {
if (s.indexOf("/") > -1) {
s = s.substring(s.indexOf("/") + 1).trim();
}
Object oldExit = Model.getFacade().getExit(st);
if (oldExit == null) {
Model.getStateMachinesHelper().setExit(st, buildNewCallAction(s));
} else {
updateAction(oldExit, s);
}
}
/**
* Parse a line of the form: "do /action" and create an action.
* We do not need to check for the presence of the word "do" - that
* is done by the caller.
*
* @param st the state object
* @param s the string to be parsed
*/
private void parseStateDoAction(Object st, String s) {
if (s.indexOf("/") > -1) {
s = s.substring(s.indexOf("/") + 1).trim();
}
Object oldDo = Model.getFacade().getDoActivity(st);
if (oldDo == null) {
Model.getStateMachinesHelper().setDoActivity(st,
buildNewCallAction(s));
} else {
updateAction(oldDo, s);
}
}
/**
* This builds a CallAction with default attributes. But without Operation!
*
* @author MVW
* @param s
* string representing the Script of the Action
* @return The newly created CallAction.
*/
private Object buildNewCallAction(String s) {
Object a =
Model.getCommonBehaviorFactory().createCallAction();
Object ae =
Model.getDataTypesFactory().createActionExpression(LANGUAGE, s);
Model.getCommonBehaviorHelper().setScript(a, ae);
Model.getCoreHelper().setName(a, "anon");
return a;
}
/**
* Update an existing Action with a new Script.
*
* @author MVW
* @param old the Action
* @param s a string representing a new Script for the ActionExpression
*/
private void updateAction(Object old, String s) {
Object ae = Model.getFacade().getScript(old); // the ActionExpression
String language = LANGUAGE;
if (ae != null) {
language = Model.getDataTypesHelper().getLanguage(ae);
String body = (String) Model.getFacade().getBody(ae);
if (body.equals(s)) {
return;
}
}
ae = Model.getDataTypesFactory().createActionExpression(language, s);
Model.getCommonBehaviorHelper().setScript(old, ae);
}
/**
* This deletes modelelements, and swallows null without barking.
*
* @author Michiel
* @param obj
* the modelelement to be deleted
*/
private void delete(Object obj) {
if (obj != null) {
Model.getUmlFactory().delete(obj);
}
}
}