/* $Id: NotationUtilityUml.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.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Stack;
import org.argouml.i18n.Translator;
import org.argouml.kernel.Project;
import org.argouml.kernel.ProjectManager;
import org.argouml.kernel.ProjectSettings;
import org.argouml.model.Model;
import org.argouml.uml.StereotypeUtility;
import org.argouml.util.CustomSeparator;
import org.argouml.util.MyTokenizer;
/**
* This class is a utility for the UML notation.
*
* @author Michiel van der Wulp
*/
public final class NotationUtilityUml {
/**
* The array of special properties for attributes.
*/
static PropertySpecialString[] attributeSpecialStrings;
/**
* The list of CustomSeparators to use when tokenizing attributes.
*/
static List<CustomSeparator> attributeCustomSep;
/**
* The array of special properties for operations.
*/
static PropertySpecialString[] operationSpecialStrings;
/**
* The List of CustomSeparators to use when tokenizing attributes.
*/
static final List<CustomSeparator> operationCustomSep;
/**
* The list of CustomSeparators to use when tokenizing parameters.
*/
private static final List<CustomSeparator> parameterCustomSep;
private static final String LIST_SEPARATOR = ", ";
/**
* The character with a meaning as a visibility at the start
* of an attribute.
*/
static final String VISIBILITYCHARS = "+#-~";
/**
* The constructor.
*/
public NotationUtilityUml() { }
/* TODO: Can we put the static block within the init()? */
static {
attributeSpecialStrings = new PropertySpecialString[2];
attributeCustomSep = new ArrayList<CustomSeparator>();
attributeCustomSep.add(MyTokenizer.SINGLE_QUOTED_SEPARATOR);
attributeCustomSep.add(MyTokenizer.DOUBLE_QUOTED_SEPARATOR);
attributeCustomSep.add(MyTokenizer.PAREN_EXPR_STRING_SEPARATOR);
operationSpecialStrings = new PropertySpecialString[8];
operationCustomSep = new ArrayList<CustomSeparator>();
operationCustomSep.add(MyTokenizer.SINGLE_QUOTED_SEPARATOR);
operationCustomSep.add(MyTokenizer.DOUBLE_QUOTED_SEPARATOR);
operationCustomSep.add(MyTokenizer.PAREN_EXPR_STRING_SEPARATOR);
parameterCustomSep = new ArrayList<CustomSeparator>();
parameterCustomSep.add(MyTokenizer.SINGLE_QUOTED_SEPARATOR);
parameterCustomSep.add(MyTokenizer.DOUBLE_QUOTED_SEPARATOR);
parameterCustomSep.add(MyTokenizer.PAREN_EXPR_STRING_SEPARATOR);
}
static void init() {
int assPos = 0;
attributeSpecialStrings[assPos++] =
new PropertySpecialString("frozen",
new PropertyOperation() {
public void found(Object element, String value) {
if (Model.getFacade().isAStructuralFeature(element)) {
if (value == null) {
/* the text was: {frozen} */
Model.getCoreHelper().setReadOnly(element, true);
} else if ("false".equalsIgnoreCase(value)) {
/* the text was: {frozen = false} */
Model.getCoreHelper().setReadOnly(element, false);
} else if ("true".equalsIgnoreCase(value)) {
/* the text was: {frozen = true} */
Model.getCoreHelper().setReadOnly(element, true);
}
}
}
});
// TODO: AddOnly has been removed in UML 2.x, so we should phase out
// support of it - tfm - 20070529
attributeSpecialStrings[assPos++] =
new PropertySpecialString("addonly",
new PropertyOperation() {
public void found(Object element, String value) {
if (Model.getFacade().isAStructuralFeature(element)) {
if ("false".equalsIgnoreCase(value)) {
Model.getCoreHelper().setReadOnly(element, true);
} else {
Model.getCoreHelper().setChangeability(element,
Model.getChangeableKind().getAddOnly());
}
}
}
});
assert assPos == attributeSpecialStrings.length;
operationSpecialStrings = new PropertySpecialString[8];
int ossPos = 0;
operationSpecialStrings[ossPos++] =
new PropertySpecialString("sequential",
new PropertyOperation() {
public void found(Object element, String value) {
if (Model.getFacade().isAOperation(element)) {
Model.getCoreHelper().setConcurrency(element,
Model.getConcurrencyKind().getSequential());
}
}
});
operationSpecialStrings[ossPos++] =
new PropertySpecialString("guarded",
new PropertyOperation() {
public void found(Object element, String value) {
Object kind = Model.getConcurrencyKind().getGuarded();
if (value != null && value.equalsIgnoreCase("false")) {
kind = Model.getConcurrencyKind().getSequential();
}
if (Model.getFacade().isAOperation(element)) {
Model.getCoreHelper().setConcurrency(element, kind);
}
}
});
operationSpecialStrings[ossPos++] =
new PropertySpecialString("concurrent",
new PropertyOperation() {
public void found(Object element, String value) {
Object kind =
Model.getConcurrencyKind().getConcurrent();
if (value != null && value.equalsIgnoreCase("false")) {
kind = Model.getConcurrencyKind().getSequential();
}
if (Model.getFacade().isAOperation(element)) {
Model.getCoreHelper().setConcurrency(element, kind);
}
}
});
operationSpecialStrings[ossPos++] =
new PropertySpecialString("concurrency",
new PropertyOperation() {
public void found(Object element, String value) {
Object kind =
Model.getConcurrencyKind().getSequential();
if ("guarded".equalsIgnoreCase(value)) {
kind = Model.getConcurrencyKind().getGuarded();
} else if ("concurrent".equalsIgnoreCase(value)) {
kind = Model.getConcurrencyKind().getConcurrent();
}
if (Model.getFacade().isAOperation(element)) {
Model.getCoreHelper().setConcurrency(element, kind);
}
}
});
operationSpecialStrings[ossPos++] =
new PropertySpecialString("abstract",
new PropertyOperation() {
public void found(Object element, String value) {
boolean isAbstract = true;
if (value != null && value.equalsIgnoreCase("false")) {
isAbstract = false;
}
if (Model.getFacade().isAOperation(element)) {
Model.getCoreHelper().setAbstract(
element,
isAbstract);
}
}
});
operationSpecialStrings[ossPos++] =
new PropertySpecialString("leaf",
new PropertyOperation() {
public void found(Object element, String value) {
boolean isLeaf = true;
if (value != null && value.equalsIgnoreCase("false")) {
isLeaf = false;
}
if (Model.getFacade().isAOperation(element)) {
Model.getCoreHelper().setLeaf(element, isLeaf);
}
}
});
operationSpecialStrings[ossPos++] =
new PropertySpecialString("query",
new PropertyOperation() {
public void found(Object element, String value) {
boolean isQuery = true;
if (value != null && value.equalsIgnoreCase("false")) {
isQuery = false;
}
if (Model.getFacade().isABehavioralFeature(element)) {
Model.getCoreHelper().setQuery(element, isQuery);
}
}
});
operationSpecialStrings[ossPos++] =
new PropertySpecialString("root",
new PropertyOperation() {
public void found(Object element, String value) {
boolean isRoot = true;
if (value != null && value.equalsIgnoreCase("false")) {
isRoot = false;
}
if (Model.getFacade().isAOperation(element)) {
Model.getCoreHelper().setRoot(element, isRoot);
}
}
});
assert ossPos == operationSpecialStrings.length;
}
/**
* Parse a string on the format:
* <pre>
* [ << stereotype >>] [+|-|#|~] [full_pathname ::] [name]
* </pre>
*
* @param me The ModelElement <em>text</em> describes.
* @param text A String on the above format.
* @throws ParseException
* when it detects an error in the attribute string. See also
* ParseError.getErrorOffset().
*/
protected static void parseModelElement(Object me, String text)
throws ParseException {
MyTokenizer st;
List<String> path = null;
String name = null;
StringBuilder stereotype = null;
String token;
try {
st = new MyTokenizer(text, "<<,\u00AB,\u00BB,>>,::");
while (st.hasMoreTokens()) {
token = st.nextToken();
if ("<<".equals(token) || "\u00AB".equals(token)) {
if (stereotype != null) {
String msg =
"parsing.error.model-element-name.twin-stereotypes";
throw new ParseException(Translator.localize(msg),
st.getTokenIndex());
}
stereotype = new StringBuilder();
while (true) {
token = st.nextToken();
if (">>".equals(token) || "\u00BB".equals(token)) {
break;
}
stereotype.append(token);
}
} else if ("::".equals(token)) {
if (name != null) {
name = name.trim();
}
if (path != null && (name == null || "".equals(name))) {
String msg =
"parsing.error.model-element-name.anon-qualifiers";
throw new ParseException(Translator.localize(msg),
st.getTokenIndex());
}
if (path == null) {
path = new ArrayList<String>();
}
if (name != null) {
path.add(name);
}
name = null;
} else {
if (name != null) {
String msg =
"parsing.error.model-element-name.twin-names";
throw new ParseException(Translator.localize(msg),
st.getTokenIndex());
}
name = token;
}
}
} catch (NoSuchElementException nsee) {
String msg =
"parsing.error.model-element-name.unexpected-name-element";
throw new ParseException(Translator.localize(msg),
text.length());
} catch (ParseException pre) {
throw pre;
}
if (name != null) {
name = name.trim();
}
if (path != null && (name == null || "".equals(name))) {
String msg = "parsing.error.model-element-name.must-end-with-name";
throw new ParseException(Translator.localize(msg), 0);
}
if (name != null && name.startsWith("+")) {
name = name.substring(1).trim();
Model.getCoreHelper().setVisibility(me,
Model.getVisibilityKind().getPublic());
}
if (name != null && name.startsWith("-")) {
name = name.substring(1).trim();
Model.getCoreHelper().setVisibility(me,
Model.getVisibilityKind().getPrivate());
}
if (name != null && name.startsWith("#")) {
name = name.substring(1).trim();
Model.getCoreHelper().setVisibility(me,
Model.getVisibilityKind().getProtected());
}
if (name != null && name.startsWith("~")) {
name = name.substring(1).trim();
Model.getCoreHelper().setVisibility(me,
Model.getVisibilityKind().getPackage());
}
if (name != null) {
Model.getCoreHelper().setName(me, name);
}
StereotypeUtility.dealWithStereotypes(me, stereotype, false);
if (path != null) {
Object nspe =
Model.getModelManagementHelper().getElement(
path,
Model.getFacade().getRoot(me));
if (nspe == null || !(Model.getFacade().isANamespace(nspe))) {
String msg =
"parsing.error.model-element-name.namespace-unresolved";
throw new ParseException(Translator.localize(msg),
0);
}
if (!Model.getCoreHelper().isValidNamespace(me, nspe)) {
String msg =
"parsing.error.model-element-name.namespace-invalid";
throw new ParseException(Translator.localize(msg),
0);
}
Model.getCoreHelper().addOwnedElement(nspe, me);
}
}
/**
* Utility function to determine the presence of a key.
* The default is false.
*
* @param key the string for the key
* @param map the Map to check for the presence
* and value of the key
* @return true if the value for the key is true, otherwise false
*/
public static boolean isValue(final String key, final Map map) {
if (map == null) {
return false;
}
Object o = map.get(key);
if (!(o instanceof Boolean)) {
return false;
}
return ((Boolean) o).booleanValue();
}
/**
* Returns a visibility String either for a VisibilityKind or a model
* element.
*
* @param o a modelelement or a visibilitykind
* @return a string. May be the empty string, but guaranteed not to be null
*/
public static String generateVisibility2(Object o) {
if (o == null) {
return "";
}
if (Model.getFacade().isANamedElement(o)) {
if (Model.getFacade().isPublic(o)) {
return "+";
}
if (Model.getFacade().isPrivate(o)) {
return "-";
}
if (Model.getFacade().isProtected(o)) {
return "#";
}
if (Model.getFacade().isPackage(o)) {
return "~";
}
}
if (Model.getFacade().isAVisibilityKind(o)) {
if (Model.getVisibilityKind().getPublic().equals(o)) {
return "+";
}
if (Model.getVisibilityKind().getPrivate().equals(o)) {
return "-";
}
if (Model.getVisibilityKind().getProtected().equals(o)) {
return "#";
}
if (Model.getVisibilityKind().getPackage().equals(o)) {
return "~";
}
}
return "";
}
/**
* @param modelElement the UML element to generate for
* @return a string which represents the path
*/
protected static String generatePath(Object modelElement) {
StringBuilder s = new StringBuilder();
Object p = modelElement;
Stack<String> stack = new Stack<String>();
Object ns = Model.getFacade().getNamespace(p);
while (ns != null && !Model.getFacade().isAModel(ns)) {
stack.push(Model.getFacade().getName(ns));
ns = Model.getFacade().getNamespace(ns);
}
while (!stack.isEmpty()) {
s.append(stack.pop() + "::");
}
if (s.length() > 0 && !(s.lastIndexOf(":") == s.length() - 1)) {
s.append("::");
}
return s.toString();
}
/**
* Parses a parameter list and aligns the parameter list in op to that
* specified in param. A parameter list generally has the following syntax:
*
* <pre>
* param := [inout] [name] [: type] [= initial value]
* list := [param] [, param]*
* </pre>
*
* <code>inout</code> is optional and if omitted the old value preserved.
* If no value has been assigned, then <code>in </code> is assumed.<p>
*
* <code>name</code>, <code>type</code> and <code>initial value</code>
* are optional and if omitted the old value preserved.<p>
*
* <code>type</code> and <code>initial value</code> can be given
* in any order.<p>
*
* Unspecified properties is carried over by position, so if a parameter is
* inserted into the list, then it will inherit properties from the
* parameter that was there before for unspecified properties.<p>
*
* This syntax is compatible with the UML 1.3 specification.
*
* @param op
* The operation the parameter list belongs to.
* @param param
* The parameter list, without enclosing parentheses.
* @param paramOffset
* The offset to the beginning of the parameter list. Used for
* error reports.
* @throws java.text.ParseException
* when it detects an error in the attribute string. See also
* ParseError.getErrorOffset().
*/
static void parseParamList(Object op, String param, int paramOffset)
throws ParseException {
MyTokenizer st =
new MyTokenizer(param, " ,\t,:,=,\\,", parameterCustomSep);
// Copy returned parameters because it will be a live collection for MDR
Collection origParam =
new ArrayList(Model.getFacade().getParameters(op));
Object ns = Model.getFacade().getRoot(op);
if (Model.getFacade().isAOperation(op)) {
Object ow = Model.getFacade().getOwner(op);
if (ow != null && Model.getFacade().getNamespace(ow) != null) {
ns = Model.getFacade().getNamespace(ow);
}
}
Iterator it = origParam.iterator();
while (st.hasMoreTokens()) {
String kind = null;
String name = null;
String tok;
String type = null;
StringBuilder value = null;
Object p = null;
boolean hasColon = false;
boolean hasEq = false;
while (it.hasNext() && p == null) {
p = it.next();
if (Model.getFacade().isReturn(p)) {
p = null;
}
}
while (st.hasMoreTokens()) {
tok = st.nextToken();
if (",".equals(tok)) {
break;
} else if (" ".equals(tok) || "\t".equals(tok)) {
if (hasEq) {
value.append(tok);
}
} else if (":".equals(tok)) {
hasColon = true;
hasEq = false;
} else if ("=".equals(tok)) {
if (value != null) {
String msg =
"parsing.error.notation-utility.two-default-values";
throw new ParseException(Translator.localize(msg),
paramOffset + st.getTokenIndex());
}
hasEq = true;
hasColon = false;
value = new StringBuilder();
} else if (hasColon) {
if (type != null) {
String msg = "parsing.error.notation-utility.two-types";
throw new ParseException(Translator.localize(msg),
paramOffset + st.getTokenIndex());
}
if (tok.charAt(0) == '\'' || tok.charAt(0) == '\"') {
String msg =
"parsing.error.notation-utility.type-quoted";
throw new ParseException(Translator.localize(msg),
paramOffset + st.getTokenIndex());
}
if (tok.charAt(0) == '(') {
String msg =
"parsing.error.notation-utility.type-expr";
throw new ParseException(Translator.localize(msg),
paramOffset + st.getTokenIndex());
}
type = tok;
} else if (hasEq) {
value.append(tok);
} else {
if (name != null && kind != null) {
String msg =
"parsing.error.notation-utility.extra-text";
throw new ParseException(Translator.localize(msg),
paramOffset + st.getTokenIndex());
}
if (tok.charAt(0) == '\'' || tok.charAt(0) == '\"') {
String msg =
"parsing.error.notation-utility.name-kind-quoted";
throw new ParseException(
Translator.localize(msg),
paramOffset + st.getTokenIndex());
}
if (tok.charAt(0) == '(') {
String msg =
"parsing.error.notation-utility.name-kind-expr";
throw new ParseException(
Translator.localize(msg),
paramOffset + st.getTokenIndex());
}
kind = name;
name = tok;
}
}
if (p == null) {
/* Leave the type undefined (see issue 6145): */
p = Model.getCoreFactory().buildParameter(op, null);
}
if (name != null) {
Model.getCoreHelper().setName(p, name.trim());
}
if (kind != null) {
setParamKind(p, kind.trim());
}
if (type != null) {
Model.getCoreHelper().setType(p, getType(type.trim(), ns));
}
if (value != null) {
// TODO: Find a better default language
// TODO: We should know the notation language, since it is us
Project project =
ProjectManager.getManager().getCurrentProject();
ProjectSettings ps = project.getProjectSettings();
String notationLanguage = ps.getNotationLanguage();
Object initExpr =
Model.getDataTypesFactory()
.createExpression(
notationLanguage,
value.toString().trim());
Model.getCoreHelper().setDefaultValue(p, initExpr);
}
}
while (it.hasNext()) {
Object p = it.next();
if (!Model.getFacade().isReturn(p)) {
Model.getCoreHelper().removeParameter(op, p);
}
}
}
/**
* Set a parameters kind according to a string description of
* that kind.
* @param parameter the parameter
* @param description the string description
*/
private static void setParamKind(Object parameter, String description) {
Object kind;
if ("out".equalsIgnoreCase(description)) {
kind = Model.getDirectionKind().getOutParameter();
} else if ("inout".equalsIgnoreCase(description)) {
kind = Model.getDirectionKind().getInOutParameter();
} else {
kind = Model.getDirectionKind().getInParameter();
}
Model.getCoreHelper().setKind(parameter, kind);
}
/**
* Finds the classifier associated with the type named in name.
*
* @param name
* The name of the type to get.
* @param defaultSpace
* The default name-space to place the type in.
* @return The classifier associated with the name.
*/
static Object getType(String name, Object defaultSpace) {
Object type = null;
Project p = ProjectManager.getManager().getCurrentProject();
// Should we be getting this from the GUI? BT 11 aug 2002
type = p.findType(name, false);
if (type == null) { // no type defined yet
type = Model.getCoreFactory().buildClass(name,
defaultSpace);
}
return type;
}
/**
* Applies a List of name/value pairs of properties to a model element.
* The name is treated as the tag of a tagged value unless it is one of the
* PropertySpecialStrings, in which case the action of the
* PropertySpecialString is invoked.
*
* @param elem
* An model element to apply the properties to.
* @param prop
* A List with name, value pairs of properties.
* @param spec
* An array of PropertySpecialStrings to use.
*/
static void setProperties(Object elem, List<String> prop,
PropertySpecialString[] spec) {
String name;
String value;
int i, j;
nextProp:
for (i = 0; i + 1 < prop.size(); i += 2) {
name = prop.get(i);
value = prop.get(i + 1);
if (name == null) {
continue;
}
name = name.trim();
if (value != null) {
value = value.trim();
}
/* If the current property occurs a second time
* in the given list of properties, then skip it: */
for (j = i + 2; j < prop.size(); j += 2) {
String s = prop.get(j);
if (s != null && name.equalsIgnoreCase(s.trim())) {
continue nextProp;
}
}
if (spec != null) {
for (j = 0; j < spec.length; j++) {
if (spec[j].invoke(elem, name, value)) {
continue nextProp;
}
}
}
Model.getCoreHelper().setTaggedValue(elem, name, value);
}
}
/**
* Interface specifying the operation to take when a
* PropertySpecialString is matched.
*
* @author Michael Stockman
* @since 0.11.2
* @see PropertySpecialString
*/
interface PropertyOperation {
/**
* Invoked by PropertySpecialString when it has matched a property name.
*
* @param element
* The element on which the property was set.
* @param value
* The value of the property,
* may be null if no value was given.
*/
void found(Object element, String value);
}
/**
* Declares a string that should take special action when it is found
* as a property in
* {@link ParserDisplay#setProperties ParserDisplay.setProperties}.<p>
*
* <em>Example:</em>
*
* <pre>
* attributeSpecialStrings[0] =
* new PropertySpecialString("frozen",
* new PropertyOperation() {
* public void found(Object element, String value) {
* if (Model.getFacade().isAStructuralFeature(element))
* Model.getFacade().setChangeable(element,
* (value != null && value
* .equalsIgnoreCase("false")));
* }
* });
* </pre>
*
* Taken from the (former) ParserDisplay constructor.
* It creates a PropertySpecialString that is invoken when the String
* "frozen" is found as a property name. Then
* the found mehod in the anonymous inner class
* defined on the 2nd line is invoked and performs
* a custom action on the element on which the property was
* specified by the user. In this case it does a setChangeability
* on an attribute instead of setting a tagged value,
* which would not have the desired effect.
*
* @author Michael Stockman
* @since 0.11.2
* @see PropertyOperation
* @see ParserDisplay#setProperties
*/
static class PropertySpecialString {
private String name;
private PropertyOperation op;
/**
* Constructs a new PropertySpecialString that will invoke the
* action in propop when {@link #invoke(Object, String, String)} is
* called with name equal to str and then return true from invoke.
*
* @param str
* The name of this PropertySpecialString.
* @param propop
* An object containing the method to invoke on a match.
*/
public PropertySpecialString(String str, PropertyOperation propop) {
name = str;
op = propop;
}
/**
* Called by {@link NotationUtilityUml#setProperties(Object,
* java.util.Vector, PropertySpecialString[])} while
* searching for an action to
* invoke for a property. If it returns true, then setProperties
* may assume that all required actions have been taken and stop
* searching.
*
* @param pname
* The name of a property.
* @param value
* The value of a property.
* @param element
* A model element to apply the properties to.
* @return <code>true</code> if an action is performed, otherwise
* <code>false</code>.
*/
boolean invoke(Object element, String pname, String value) {
if (!name.equalsIgnoreCase(pname)) {
return false;
}
op.found(element, value);
return true;
}
}
/**
* Checks for ';' in Strings or chars in ';' separated tokens in order to
* return an index to the next attribute or operation substring, -1
* otherwise (a ';' inside a String or char delimiters is ignored).
*
* @param s The string to search.
* @param start The position to start at.
* @return the index to the next attribute
*/
static int indexOfNextCheckedSemicolon(String s, int start) {
if (s == null || start < 0 || start >= s.length()) {
return -1;
}
int end;
boolean inside = false;
boolean backslashed = false;
char c;
for (end = start; end < s.length(); end++) {
c = s.charAt(end);
if (!inside && c == ';') {
return end;
} else if (!backslashed && (c == '\'' || c == '\"')) {
inside = !inside;
}
backslashed = (!backslashed && c == '\\');
}
return end;
}
/**
* Finds a visibility for the visibility specified by name. If no known
* visibility can be deduced, private visibility is used.
*
* @param name
* The Java name of the visibility.
* @return A visibility corresponding to name.
*/
static Object getVisibility(String name) {
if ("+".equals(name) || "public".equals(name)) {
return Model.getVisibilityKind().getPublic();
} else if ("#".equals(name) || "protected".equals(name)) {
return Model.getVisibilityKind().getProtected();
} else if ("~".equals(name) || "package".equals(name)) {
return Model.getVisibilityKind().getPackage();
} else {
/* if ("-".equals(name) || "private".equals(name)) */
return Model.getVisibilityKind().getPrivate();
}
}
/**
* Generate the text for one or more stereotype(s).
*
* @param st One of:
* <ul>
* <li>a stereotype UML object</li>
* <li>a string</li>
* <li>a collection of stereotypes</li>
* <li>a modelelement of which the stereotypes are retrieved</li>
* </ul>
* @param useGuillemets true if Unicode double angle bracket quote
* characters should be used.
* @return fully formatted string with list of stereotypes separated by
* commas and surround in brackets
*/
public static String generateStereotype(Object st, boolean useGuillemets) {
if (st == null) {
return "";
}
if (st instanceof String) {
return formatStereotype((String) st, useGuillemets);
}
if (Model.getFacade().isAStereotype(st)) {
return formatStereotype(Model.getFacade().getName(st),
useGuillemets);
}
if (Model.getFacade().isAModelElement(st)) {
st = Model.getFacade().getStereotypes(st);
}
if (st instanceof Collection) {
String result = null;
boolean found = false;
for (Object stereotype : (Collection) st) {
String name = Model.getFacade().getName(stereotype);
if (!found) {
result = name;
found = true;
} else {
// Allow concatenation order and separator to be localized
result = Translator.localize("misc.stereo.concatenate",
new Object[] {result, name});
}
}
if (found) {
return formatStereotype(result, useGuillemets);
}
}
return "";
}
/**
* Create a string representation of a stereotype, keyword or comma separate
* list of names. This method just wraps the string in <<angle brackets>> or
* guillemets (double angle bracket characters) depending on the setting
* of the flag <code>useGuillemets</code>.
*
* @param name the name of the stereotype
* @param useGuillemets true if Unicode double angle bracket quote
* characters should be used.
* @return the string representation
*/
public static String formatStereotype(String name, boolean useGuillemets) {
if (name == null || name.length() == 0) {
return "";
}
String key = "misc.stereo.guillemets."
+ Boolean.toString(useGuillemets);
return Translator.localize(key, new Object[] {name});
}
/**
* Generates the representation of a parameter on the display (diagram). The
* string to be returned will have the following syntax:
* <p>
*
* kind name : type-expression = default-value
*
* @see org.argouml.notation.NotationProvider2#generateParameter(java.lang.Object)
*/
static String generateParameter(Object parameter) {
StringBuffer s = new StringBuffer();
s.append(generateKind(Model.getFacade().getKind(parameter)));
if (s.length() > 0) {
s.append(" ");
}
s.append(Model.getFacade().getName(parameter));
String classRef =
generateClassifierRef(Model.getFacade().getType(parameter));
if (classRef.length() > 0) {
s.append(" : ");
s.append(classRef);
}
String defaultValue =
generateExpression(Model.getFacade().getDefaultValue(parameter));
if (defaultValue.length() > 0) {
s.append(" = ");
s.append(defaultValue);
}
return s.toString();
}
private static String generateExpression(Object expr) {
if (Model.getFacade().isAExpression(expr)) {
return generateUninterpreted(
(String) Model.getFacade().getBody(expr));
} else if (Model.getFacade().isAConstraint(expr)) {
return generateExpression(Model.getFacade().getBody(expr));
}
return "";
}
private static String generateUninterpreted(String un) {
if (un == null) {
return "";
}
return un;
}
private static String generateClassifierRef(Object cls) {
if (cls == null) {
return "";
}
return Model.getFacade().getName(cls);
}
private static String generateKind(Object /*Parameter etc.*/ kind) {
StringBuffer s = new StringBuffer();
// TODO: I18N
if (kind == null /* "in" is the default */
|| kind == Model.getDirectionKind().getInParameter()) {
s.append(/*"in"*/ ""); /* See issue 3421. */
} else if (kind == Model.getDirectionKind().getInOutParameter()) {
s.append("inout");
} else if (kind == Model.getDirectionKind().getReturnParameter()) {
// return nothing
} else if (kind == Model.getDirectionKind().getOutParameter()) {
s.append("out");
}
return s.toString();
}
/**
* @param tv a tagged value
* @return a string that represents the tagged value
*/
static String generateTaggedValue(Object tv) {
if (tv == null) {
return "";
}
return Model.getFacade().getTagOfTag(tv)
+ "="
+ generateUninterpreted(Model.getFacade().getValueOfTag(tv));
}
/**
* Generate the text of a multiplicity.
*
* @param element a multiplicity or an element which has a multiplicity
* @param showSingularMultiplicity if false return the empty string for 1..1
* multiplicities.
* @return a string containing the formatted multiplicity,
* or the empty string
*/
public static String generateMultiplicity(Object element,
boolean showSingularMultiplicity) {
Object multiplicity;
if (Model.getFacade().isAMultiplicity(element)) {
multiplicity = element;
} else if (Model.getFacade().isAUMLElement(element)) {
multiplicity = Model.getFacade().getMultiplicity(element);
} else {
throw new IllegalArgumentException();
}
// it can still be null if the UML element
// did not have a multiplicity defined.
if (multiplicity != null) {
int upper = Model.getFacade().getUpper(multiplicity);
int lower = Model.getFacade().getLower(multiplicity);
if (lower != 1 || upper != 1 || showSingularMultiplicity) {
// TODO: I18N
return Model.getFacade().toString(multiplicity);
}
}
return "";
}
/**
* @param umlAction the action
* @return the generated text (never null)
*/
static String generateAction(Object umlAction) {
Collection c;
Iterator it;
String s;
StringBuilder p;
boolean first;
if (umlAction == null) {
return "";
}
Object script = Model.getFacade().getScript(umlAction);
if ((script != null) && (Model.getFacade().getBody(script) != null)) {
s = Model.getFacade().getBody(script).toString();
} else {
s = "";
}
p = new StringBuilder();
c = Model.getFacade().getActualArguments(umlAction);
if (c != null) {
it = c.iterator();
first = true;
while (it.hasNext()) {
Object arg = it.next();
if (!first) {
// TODO: I18N
p.append(", ");
}
if (Model.getFacade().getValue(arg) != null) {
p.append(generateExpression(
Model.getFacade().getValue(arg)));
}
first = false;
}
}
if (s.length() == 0 && p.length() == 0) {
return "";
}
/* If there are no arguments, then do not show the ().
* This solves issue 1758.
* Arguments are not supported anyhow in the UI yet.
* These brackets are easily confused with the brackets
* for the Operation of a CallAction.
*/
if (p.length() == 0) {
return s;
}
// TODO: I18N
return s + " (" + p + ")";
}
/**
* Generate a textual representation of the given Action or ActionSequence
* according the UML standard notation.
*
* @param a the UML Action or ActionSequence
* @return the generated textual representation
* of the given action(sequence).
* This value is guaranteed NOT null.
*/
public static String generateActionSequence(Object a) {
if (Model.getFacade().isAActionSequence(a)) {
StringBuffer str = new StringBuffer("");
Collection actions = Model.getFacade().getActions(a);
Iterator i = actions.iterator();
if (i.hasNext()) {
str.append(generateAction(i.next()));
}
while (i.hasNext()) {
str.append("; ");
str.append(generateAction(i.next()));
}
return str.toString();
} else {
return generateAction(a);
}
}
static StringBuilder formatNameList(Collection modelElements) {
return formatNameList(modelElements, LIST_SEPARATOR);
}
static StringBuilder formatNameList(Collection modelElements,
String separator) {
StringBuilder result = new StringBuilder();
for (Object element : modelElements) {
String name = Model.getFacade().getName(element);
// TODO: Any special handling for null names? append will use "null"
result.append(name).append(separator);
}
if (result.length() >= separator.length()) {
result.delete(result.length() - separator.length(),
result.length());
}
return result;
}
}