/* $Id: OperationNotationUml.java 17828 2010-01-12 18:55:12Z linus $ ***************************************************************************** * Copyright (c) 2009 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: * mvw ***************************************************************************** * * 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.Iterator; import java.util.List; import java.util.NoSuchElementException; 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.kernel.Project; import org.argouml.kernel.ProjectManager; import org.argouml.model.InvalidElementException; import org.argouml.model.Model; import org.argouml.notation.NotationSettings; import org.argouml.notation.providers.OperationNotation; import org.argouml.uml.StereotypeUtility; import org.argouml.util.MyTokenizer; /** * The UML notation for an Operation or a Reception. * * @author mvw@tigris.org */ public class OperationNotationUml extends OperationNotation { private static final String RECEPTION_KEYWORD = "signal"; /** * The constructor. * * @param operation the operation that is represented */ public OperationNotationUml(Object operation) { super(operation); } /* * @see org.argouml.notation.providers.NotationProvider#parse(java.lang.Object, java.lang.String) */ public void parse(Object modelElement, String text) { try { parseOperationFig(Model.getFacade().getOwner(modelElement), modelElement, text); } catch (ParseException pe) { String msg = "statusmsg.bar.error.parsing.operation"; Object[] args = { pe.getLocalizedMessage(), Integer.valueOf(pe.getErrorOffset()), }; ArgoEventPump.fireEvent(new ArgoHelpEvent( ArgoEventTypes.HELP_CHANGED, this, Translator.messageFormat(msg, args))); } } /** * Parse a string representing one ore more ';' separated operations. The * case that a String or char contains a ';' (e.g. in an initializer) is * handled, but not other occurences of ';'. * * @param classifier Classifier The classifier the operation(s) belong to * @param operation Operation The operation on which the editing happened * @param text The string to parse * @throws ParseException for invalid input */ public void parseOperationFig( Object classifier, Object operation, String text) throws ParseException { if (classifier == null || operation == null) { return; } ParseException pex = null; int start = 0; int end = NotationUtilityUml.indexOfNextCheckedSemicolon(text, start); Project currentProject = ProjectManager.getManager().getCurrentProject(); if (end == -1) { //no text? remove op! currentProject.moveToTrash(operation); return; } String s = text.substring(start, end).trim(); if (s.length() == 0) { //no non-whitechars in text? remove op! currentProject.moveToTrash(operation); return; } parseOperation(s, operation); int i = Model.getFacade().getFeatures(classifier).indexOf(operation); // check for more operations (';' separated): start = end + 1; end = NotationUtilityUml.indexOfNextCheckedSemicolon(text, start); while (end > start && end <= text.length()) { s = text.substring(start, end).trim(); if (s.length() > 0) { // yes, there are more: Object returnType = currentProject.getDefaultReturnType(); Object newOp = Model.getCoreFactory() .buildOperation(classifier, returnType); if (newOp != null) { try { parseOperation(s, newOp); //newOp.setOwnerScope(op.getOwnerScope()); // //not needed in case of operation if (i != -1) { Model.getCoreHelper().addFeature( classifier, ++i, newOp); } else { Model.getCoreHelper().addFeature( classifier, newOp); } } catch (ParseException ex) { if (pex == null) { pex = ex; } } } } start = end + 1; end = NotationUtilityUml.indexOfNextCheckedSemicolon(text, start); } if (pex != null) { throw pex; } } /** * Parse a line of text and aligns the Operation to the specification * given. The line should be on the following form:<ul> * <li> visibility name (parameter list) : return-type-expression * {property-string} * </ul> * * All elements are optional and, if left unspecified, will preserve their * old values.<p> * * <em>Stereotypes</em> can be given between any element in the line on the * form: <<stereotype1,stereotype2,stereotype3>><p> * * The following properties are recognized to have special meaning: * abstract, concurrency, concurrent, guarded, leaf, query, root and * sequential.<p> * * This syntax is compatible with the UML 1.3 spec.<p> * * (formerly visibility name (parameter list) : return-type-expression * {property-string} ) (formerly 2nd: [visibility] [keywords] returntype * name(params)[;] ) * * @param s The String to parse. * @param op The Operation to adjust to the specification in s. * @throws ParseException * when it detects an error in the attribute string. See also * ParseError.getErrorOffset(). */ public void parseOperation(String s, Object op) throws ParseException { MyTokenizer st; boolean hasColon = false; String name = null; String parameterlist = null; StringBuilder stereotype = null; String token; String type = null; String visibility = null; List<String> properties = null; int paramOffset = 0; s = s.trim(); if (s.length() > 0 && NotationUtilityUml.VISIBILITYCHARS.indexOf(s.charAt(0)) >= 0) { visibility = s.substring(0, 1); s = s.substring(1); } try { st = new MyTokenizer(s, " ,\t,<<,\u00AB,\u00BB,>>,:,=,{,},\\,", NotationUtilityUml.operationCustomSep); while (st.hasMoreTokens()) { token = st.nextToken(); if (" ".equals(token) || "\t".equals(token) || ",".equals(token)) { continue; // Do nothing } else if ("<<".equals(token) || "\u00AB".equals(token)) { if (stereotype != null) { parseError("operation.stereotypes", st.getTokenIndex()); } stereotype = new StringBuilder(); while (true) { token = st.nextToken(); if (">>".equals(token) || "\u00BB".equals(token)) { break; } stereotype.append(token); } } else if ("{".equals(token)) { properties = tokenOpenBrace(st, properties); } else if (":".equals(token)) { hasColon = true; } else if ("=".equals(token)) { parseError("operation.default-values", st.getTokenIndex()); } else if (token.charAt(0) == '(' && !hasColon) { if (parameterlist != null) { parseError("operation.two-parameter-lists", st.getTokenIndex()); } parameterlist = token; } else { if (hasColon) { if (type != null) { parseError("operation.two-types", st.getTokenIndex()); } if (token.length() > 0 && (token.charAt(0) == '\"' || token.charAt(0) == '\'')) { parseError("operation.type-quoted", st.getTokenIndex()); } if (token.length() > 0 && token.charAt(0) == '(') { parseError("operation.type-expr", st.getTokenIndex()); } type = token; } else { if (name != null && visibility != null) { parseError("operation.extra-text", st.getTokenIndex()); } if (token.length() > 0 && (token.charAt(0) == '\"' || token.charAt(0) == '\'')) { parseError("operation.name-quoted", st.getTokenIndex()); } if (token.length() > 0 && token.charAt(0) == '(') { parseError("operation.name-expr", st.getTokenIndex()); } if (name == null && visibility == null && token.length() > 1 && NotationUtilityUml.VISIBILITYCHARS.indexOf( token.charAt(0)) >= 0) { visibility = token.substring(0, 1); token = token.substring(1); } if (name != null) { visibility = name; name = token; } else { name = token; } } } } // end while loop } catch (NoSuchElementException nsee) { parseError("operation.unexpected-end-operation", s.length()); } catch (ParseException pre) { throw pre; } if (parameterlist != null) { // parameterlist is guaranteed to contain at least "(" if (parameterlist.charAt(parameterlist.length() - 1) != ')') { parseError("operation.parameter-list-incomplete", paramOffset + parameterlist.length() - 1); } paramOffset++; parameterlist = parameterlist.substring(1, parameterlist.length() - 1); NotationUtilityUml.parseParamList(op, parameterlist, paramOffset); } if (visibility != null) { Model.getCoreHelper().setVisibility(op, NotationUtilityUml.getVisibility(visibility.trim())); } if (name != null) { Model.getCoreHelper().setName(op, name.trim()); } else if (Model.getFacade().getName(op) == null || "".equals(Model.getFacade().getName(op))) { Model.getCoreHelper().setName(op, "anonymous"); } if (type != null) { Object ow = Model.getFacade().getOwner(op); Object ns = null; if (ow != null && Model.getFacade().getNamespace(ow) != null) { ns = Model.getFacade().getNamespace(ow); } else { ns = Model.getFacade().getRoot(op); } Object mtype = NotationUtilityUml.getType(type.trim(), ns); setReturnParameter(op, mtype); } if (properties != null) { NotationUtilityUml.setProperties(op, properties, NotationUtilityUml.operationSpecialStrings); } // Don't create a stereotype for <<signal>> on a Reception // but create any other parsed stereotypes as needed if (!Model.getFacade().isAReception(op) || !RECEPTION_KEYWORD.equals(stereotype.toString())) { StereotypeUtility.dealWithStereotypes(op, stereotype, true); } } /** * Convenience method to signal a parser error. * * @param message * string containing error message literal. It will be appended * to the base "parser.error." and localized. * @param offset * offset to where error occurred * @throws ParseException */ private void parseError(String message, int offset) throws ParseException { throw new ParseException( Translator.localize("parsing.error." + message), offset); } /** * Parse tokens following an open brace (properties). * * @param st tokenizer being used * @param properties current properties list * @return updated list of properties * @throws ParseException */ private List<String> tokenOpenBrace(MyTokenizer st, List<String> properties) throws ParseException { String token; StringBuilder propname = new StringBuilder(); String propvalue = null; if (properties == null) { properties = new ArrayList<String>(); } while (true) { token = st.nextToken(); if (",".equals(token) || "}".equals(token)) { if (propname.length() > 0) { properties.add(propname.toString()); properties.add(propvalue); } propname = new StringBuilder(); propvalue = null; if ("}".equals(token)) { break; } } else if ("=".equals(token)) { if (propvalue != null) { String msg = "parsing.error.operation.prop-stereotypes"; Object[] args = {propname}; throw new ParseException( Translator.localize(msg, args), st.getTokenIndex()); } propvalue = ""; } else if (propvalue == null) { propname.append(token); } else { propvalue += token; } } if (propname.length() > 0) { properties.add(propname.toString()); properties.add(propvalue); } return properties; } /** * Sets the return parameter of op to be of type type. If there is none, one * is created. If there are many, all but one are removed. * * @param op the operation * @param type the type of the return parameter */ private void setReturnParameter(Object op, Object type) { Object param = null; Iterator it = Model.getFacade().getParameters(op).iterator(); while (it.hasNext()) { Object p = it.next(); if (Model.getFacade().isReturn(p)) { param = p; break; } } while (it.hasNext()) { Object p = it.next(); if (Model.getFacade().isReturn(p)) { ProjectManager.getManager().getCurrentProject().moveToTrash(p); } } if (param == null) { Object returnType = ProjectManager.getManager() .getCurrentProject().getDefaultReturnType(); param = Model.getCoreFactory().buildParameter(op, returnType); } Model.getCoreHelper().setType(param, type); } /* * @see org.argouml.notation.providers.NotationProvider#getParsingHelp() */ public String getParsingHelp() { return "parsing.help.operation"; } /** * Generate an operation according to the UML notation: * <pre> * stereotype visibility name (parameter-list) : * return-type-expression {property-string} * </pre> * For the return-type-expression: only the types of the return parameters * are shown. Depending on settings in Notation, visibility and * properties are shown/not shown. * * @param modelElement UML Operation element * @param settings notation settings * @return a formatted text string * @see org.argouml.notation.NotationProvider#toString(java.lang.Object, org.argouml.notation.NotationSettings) */ public String toString(Object modelElement, NotationSettings settings) { return toString(modelElement, settings.isUseGuillemets(), settings.isShowVisibilities(), settings.isShowTypes(), settings.isShowProperties()); } /** * Generate an operation according to the UML notation: * <pre> * stereotype visibility name (parameter-list) : * return-type-expression {property-string} * </pre> * For the return-type-expression: only the types of the return parameters * are shown. Depending on settings in Notation, visibility and * properties are shown/not shown. * * @author jaap.branderhorst@xs4all.nl */ private String toString(Object modelElement, boolean useGuillemets, boolean showVisibility, boolean showTypes, boolean showProperties) { try { String stereoStr = NotationUtilityUml.generateStereotype( Model.getFacade().getStereotypes(modelElement), useGuillemets); boolean isReception = Model.getFacade().isAReception(modelElement); // TODO: needs I18N if (isReception) { stereoStr = NotationUtilityUml .generateStereotype(RECEPTION_KEYWORD, useGuillemets) + " " + stereoStr; } // Unused currently // StringBuffer taggedValuesSb = getTaggedValues(modelElement); // lets concatenate it to the resulting string (genStr) StringBuffer genStr = new StringBuffer(30); if ((stereoStr != null) && (stereoStr.length() > 0)) { genStr.append(stereoStr).append(" "); } if (showVisibility) { String visStr = NotationUtilityUml .generateVisibility2(modelElement); if (visStr != null) { genStr.append(visStr); } } String nameStr = Model.getFacade().getName(modelElement); if ((nameStr != null) && (nameStr.length() > 0)) { genStr.append(nameStr); } /* The "show types" defaults to TRUE, to stay compatible with older * ArgoUML versions that did not have this setting: */ if (showTypes) { // the parameters StringBuffer parameterStr = new StringBuffer(); parameterStr.append("(").append(getParameterList(modelElement)) .append(")"); // the returnparameters StringBuffer returnParasSb = getReturnParameters(modelElement, isReception); genStr.append(parameterStr).append(" "); if ((returnParasSb != null) && (returnParasSb.length() > 0)) { genStr.append(returnParasSb).append(" "); } } else { genStr.append("()"); } if (showProperties) { StringBuffer propertySb = getProperties(modelElement, isReception); if (propertySb.length() > 0) { genStr.append(propertySb); } } return genStr.toString().trim(); } catch (InvalidElementException e) { // The model element was deleted while we were working on it return ""; } } private StringBuffer getParameterList(Object modelElement) { StringBuffer parameterListBuffer = new StringBuffer(); Collection coll = Model.getFacade().getParameters(modelElement); Iterator it = coll.iterator(); int counter = 0; while (it.hasNext()) { Object parameter = it.next(); if (!Model.getFacade().hasReturnParameterDirectionKind( parameter)) { counter++; parameterListBuffer.append( NotationUtilityUml.generateParameter(parameter)); parameterListBuffer.append(","); } } if (counter > 0) { parameterListBuffer.delete( parameterListBuffer.length() - 1, parameterListBuffer.length()); } return parameterListBuffer; } private StringBuffer getReturnParameters(Object modelElement, boolean isReception) { StringBuffer returnParasSb = new StringBuffer(); if (!isReception) { Collection coll = Model.getCoreHelper().getReturnParameters(modelElement); if (coll != null && coll.size() > 0) { returnParasSb.append(": "); Iterator it2 = coll.iterator(); while (it2.hasNext()) { Object type = Model.getFacade().getType(it2.next()); if (type != null) { returnParasSb.append(Model.getFacade() .getName(type)); } returnParasSb.append(","); } // if we have only one return value and without type, // the return param string is ": ,", we remove it if (returnParasSb.length() == 3) { returnParasSb.delete(0, returnParasSb.length()); } // else: we remove only the extra "," else { returnParasSb.delete( returnParasSb.length() - 1, returnParasSb.length()); } } } return returnParasSb; } private StringBuffer getProperties(Object modelElement, boolean isReception) { StringBuffer propertySb = new StringBuffer().append("{"); // the query state if (Model.getFacade().isQuery(modelElement)) { propertySb.append("query,"); } /* * Although Operation and Signal are peers in the UML type * hierarchy they share the attributes isRoot, isLeaf, * isAbstract, and specification. Concurrency is *not* * shared and is specific to Operation. */ if (Model.getFacade().isRoot(modelElement)) { propertySb.append("root,"); } if (Model.getFacade().isLeaf(modelElement)) { propertySb.append("leaf,"); } if (!isReception) { if (Model.getFacade().getConcurrency(modelElement) != null) { propertySb.append(Model.getFacade().getName( Model.getFacade().getConcurrency(modelElement))); propertySb.append(','); } } if (propertySb.length() > 1) { propertySb.delete(propertySb.length() - 1, propertySb.length()); // remove last , propertySb.append("}"); } else { propertySb = new StringBuffer(); } return propertySb; } private StringBuffer getTaggedValues(Object modelElement) { StringBuffer taggedValuesSb = new StringBuffer(); Iterator it3 = Model.getFacade().getTaggedValues(modelElement); if (it3 != null && it3.hasNext()) { while (it3.hasNext()) { taggedValuesSb.append( NotationUtilityUml.generateTaggedValue(it3.next())); taggedValuesSb.append(","); } taggedValuesSb.delete( taggedValuesSb.length() - 1, taggedValuesSb.length()); } return taggedValuesSb; } }