/* $Id: AttributeNotationUml.java 18760 2010-09-18 05:19:53Z tfmorris $ ***************************************************************************** * 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: * 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.List; import java.util.NoSuchElementException; import org.apache.log4j.Logger; 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.kernel.ProjectSettings; import org.argouml.model.InvalidElementException; import org.argouml.model.Model; import org.argouml.notation.NotationSettings; import org.argouml.notation.providers.AttributeNotation; import org.argouml.uml.StereotypeUtility; import org.argouml.util.MyTokenizer; /** * The notation for an attribute for UML. * * @author Michiel */ public class AttributeNotationUml extends AttributeNotation { /** * The standard error etc. logger */ private static final Logger LOG = Logger.getLogger(AttributeNotationUml.class); /** * The constructor. * @param attribute the UML object */ public AttributeNotationUml(Object attribute) { super(attribute); } /* * @see org.argouml.notation.providers.NotationProvider#parse(java.lang.Object, java.lang.String) */ public void parse(Object modelElement, String text) { try { parseAttributeFig(Model.getFacade().getOwner(modelElement), modelElement, text); } catch (ParseException pe) { String msg = "statusmsg.bar.error.parsing.attribute"; 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 attributes. The * case that a String or char contains a ';' (e.g. in an initializer) is * handled, but not other occurrences of ';'. * * @param classifier Classifier The classifier the attribute(s) belong to * @param attribute Attribute The attribute on which the editing happened * @param text The string to parse * @throws ParseException for invalid input */ public void parseAttributeFig( Object classifier, Object attribute, String text) throws ParseException { if (classifier == null || attribute == null) { return; } /* TODO: We should have all the information that is required in the * NotationSettings object */ Project project = ProjectManager.getManager().getCurrentProject(); ParseException pex = null; int start = 0; int end = NotationUtilityUml.indexOfNextCheckedSemicolon(text, start); if (end == -1) { //no text? remove attr! project.moveToTrash(attribute); return; } String s = text.substring(start, end).trim(); if (s.length() == 0) { //no non-whitechars in text? remove attr! project.moveToTrash(attribute); return; } parseAttribute(s, attribute); int i = Model.getFacade().getFeatures(classifier).indexOf(attribute); // check for more attributes (';' 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 attrType = project.getDefaultAttributeType(); Object newAttribute = Model.getUmlFactory().buildNode( Model.getMetaTypes().getAttribute()); Model.getCoreHelper().setType(newAttribute, attrType); if (newAttribute != null) { /* We need to set the namespace/owner * of the new attribute before parsing: */ if (i != -1) { Model.getCoreHelper().addFeature( classifier, ++i, newAttribute); } else { Model.getCoreHelper().addFeature( classifier, newAttribute); } try { parseAttribute(s, newAttribute); /* If the 1st attribute is static, * then the new ones, too. */ Model.getCoreHelper().setStatic( newAttribute, Model.getFacade().isStatic(attribute)); } catch (ParseException ex) { if (pex == null) { pex = ex; } } } } start = end + 1; end = NotationUtilityUml.indexOfNextCheckedSemicolon(text, start); } if (pex != null) { throw pex; } } /** * Parse a line on the form:<pre> * visibility name [: type-expression] [= initial-value] * </pre> * * <ul> * <li>If only one of visibility and name is given, then it is assumed to * be the name and the visibility is left unchanged. * <li>Type and initial value can be given in any order. * <li>Properties can be given between any element in the form<pre> * {[name] [= [value]] [, ...]} * </pre> * <li>Multiplicity can be given between any element except after the * initial-value and before the type or end (to allow java-style array * indexing in the initial value). It must be given on form [multiplicity] * with the square brackets included. * <li>Stereotypes can be given between any element except after the * initial-value and before the type or end (to allow java-style bit-shifts * in the initial value). It must be given on form * <<stereotype1,stereotype2,stereotype3>>. * </ul> * * The following properties are recognized to have special meaning: * frozen.<p> * * This syntax is compatible with the UML 1.3 spec. * * (formerly: visibility name [multiplicity] : type-expression = * initial-value {property-string} ) (2nd formerly: [visibility] [keywords] * type name [= init] [;] ) * * @param text The String to parse. * @param attribute The attribute to modify to comply * with the instructions in s. * @throws ParseException * when it detects an error in the attribute string. See also * ParseError.getErrorOffset(). */ protected void parseAttribute( String text, Object attribute) throws ParseException { StringBuilder multiplicity = null; String name = null; List<String> properties = null; StringBuilder stereotype = null; // This is null as until // the first stereotype declaration is seen. // After that it is non-null. String token; String type = null; StringBuilder value = null; String visibility = null; boolean hasColon = false; boolean hasEq = false; int multindex = -1; MyTokenizer st; text = text.trim(); if (text.length() > 0 && NotationUtilityUml.VISIBILITYCHARS.indexOf(text.charAt(0)) >= 0) { visibility = text.substring(0, 1); text = text.substring(1); } try { st = new MyTokenizer(text, " ,\t,<<,\u00AB,\u00BB,>>,[,],:,=,{,},\\,", NotationUtilityUml.attributeCustomSep); while (st.hasMoreTokens()) { token = st.nextToken(); if (" ".equals(token) || "\t".equals(token) || ",".equals(token)) { if (hasEq) { value.append(token); } } else if ("<<".equals(token) || "\u00AB".equals(token)) { if (hasEq) { value.append(token); } else { if (stereotype != null) { String msg = "parsing.error.attribute.two-sets-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 (hasEq) { value.append(token); } else { if (multiplicity != null) { String msg = "parsing.error.attribute.two-multiplicities"; throw new ParseException(Translator.localize(msg), st.getTokenIndex()); } multiplicity = new StringBuilder(); multindex = st.getTokenIndex() + 1; while (true) { token = st.nextToken(); if ("]".equals(token)) { break; } multiplicity.append(token); } } } else if ("{".equals(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.attribute.prop-two-values"; Object[] args = {propvalue}; 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); } } else if (":".equals(token)) { hasColon = true; hasEq = false; } else if ("=".equals(token)) { if (value != null) { String msg = "parsing.error.attribute.two-default-values"; throw new ParseException(Translator.localize(msg), st .getTokenIndex()); } value = new StringBuilder(); hasColon = false; hasEq = true; } else { if (hasColon) { if (type != null) { String msg = "parsing.error.attribute.two-types"; throw new ParseException(Translator.localize(msg), st.getTokenIndex()); } if (token.length() > 0 && (token.charAt(0) == '\"' || token.charAt(0) == '\'')) { String msg = "parsing.error.attribute.quoted"; throw new ParseException(Translator.localize(msg), st.getTokenIndex()); } if (token.length() > 0 && token.charAt(0) == '(') { String msg = "parsing.error.attribute.is-expr"; throw new ParseException(Translator.localize(msg), st.getTokenIndex()); } type = token; } else if (hasEq) { value.append(token); } else { if (name != null && visibility != null) { String msg = "parsing.error.attribute.extra-text"; throw new ParseException(Translator.localize(msg), st.getTokenIndex()); } if (token.length() > 0 && (token.charAt(0) == '\"' || token.charAt(0) == '\'')) { String msg = "parsing.error.attribute.name-quoted"; throw new ParseException(Translator.localize(msg), st.getTokenIndex()); } if (token.length() > 0 && token.charAt(0) == '(') { String msg = "parsing.error.attribute.name-expr"; throw new ParseException(Translator.localize(msg), 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; } } } } } catch (NoSuchElementException nsee) { String msg = "parsing.error.attribute.unexpected-end-attribute"; throw new ParseException(Translator.localize(msg), text.length()); } // catch & rethrow is not necessary if we don't do nothing (penyaskito) // catch (ParseException pre) { // throw pre; // } if (LOG.isDebugEnabled()) { LOG.debug("ParseAttribute [name: " + name + " visibility: " + visibility + " type: " + type + " value: " + value + " stereo: " + stereotype + " mult: " + multiplicity); if (properties != null) { for (int i = 0; i + 1 < properties.size(); i += 2) { LOG.debug("\tProperty [name: " + properties.get(i) + " = " + properties.get(i + 1) + "]"); } } } dealWithVisibility(attribute, visibility); dealWithName(attribute, name); dealWithType(attribute, type); dealWithValue(attribute, value); dealWithMultiplicity(attribute, multiplicity, multindex); dealWithProperties(attribute, properties); StereotypeUtility.dealWithStereotypes(attribute, stereotype, true); } private void dealWithProperties(Object attribute, List<String> properties) { if (properties != null) { NotationUtilityUml.setProperties(attribute, properties, NotationUtilityUml.attributeSpecialStrings); } } private void dealWithMultiplicity(Object attribute, StringBuilder multiplicity, int multindex) throws ParseException { if (multiplicity != null) { try { Model.getCoreHelper().setMultiplicity(attribute, multiplicity.toString()); } catch (IllegalArgumentException iae) { String msg = "parsing.error.attribute.bad-multiplicity"; Object[] args = {iae}; throw new ParseException(Translator.localize(msg, args), multindex); } } } private void dealWithValue(Object attribute, StringBuilder value) { if (value != null) { Project project = ProjectManager.getManager().getCurrentProject(); ProjectSettings ps = project.getProjectSettings(); Object initExpr = Model.getDataTypesFactory().createExpression( ps.getNotationLanguage(), value.toString().trim()); Model.getCoreHelper().setInitialValue(attribute, initExpr); } } private void dealWithType(Object attribute, String type) { if (type != null) { Object ow = Model.getFacade().getOwner(attribute); Object ns = null; if (ow != null && Model.getFacade().getNamespace(ow) != null) { ns = Model.getFacade().getNamespace(ow); } else { ns = Model.getFacade().getRoot(attribute); } Model.getCoreHelper().setType(attribute, NotationUtilityUml.getType(type.trim(), ns)); } } private void dealWithName(Object attribute, String name) { if (name != null) { Model.getCoreHelper().setName(attribute, name.trim()); } else if (Model.getFacade().getName(attribute) == null || "".equals(Model.getFacade().getName(attribute))) { Model.getCoreHelper().setName(attribute, "anonymous"); } } private void dealWithVisibility(Object attribute, String visibility) { if (visibility != null) { Model.getCoreHelper().setVisibility(attribute, NotationUtilityUml.getVisibility(visibility.trim())); } } /* * @see org.argouml.notation.providers.NotationProvider#getParsingHelp() */ public String getParsingHelp() { return "parsing.help.attribute"; } @Override public String toString(Object modelElement, NotationSettings settings) { return toString(modelElement, settings.isUseGuillemets(), settings .isShowVisibilities(), settings.isShowMultiplicities(), settings .isShowTypes(), settings.isShowInitialValues(), settings.isShowProperties()); } /* * Generates a string representation for the provided * attribute. The string representation will be of the form: * visibility name [multiplicity] : type-expression = * initial-value {property-string} * Depending on settings in Notation, visibility, multiplicity, * type-expression, initial value and properties are shown/not shown. */ private String toString(Object modelElement, boolean useGuillemets, boolean showVisibility, boolean showMultiplicity, boolean showTypes, boolean showInitialValues, boolean showProperties) { try { String stereo = NotationUtilityUml.generateStereotype(modelElement, useGuillemets); String name = Model.getFacade().getName(modelElement); String multiplicity = generateMultiplicity( Model.getFacade().getMultiplicity(modelElement)); String type = ""; // fix for loading bad projects if (Model.getFacade().getType(modelElement) != null) { type = Model.getFacade().getName( Model.getFacade().getType(modelElement)); } StringBuilder sb = new StringBuilder(20); if ((stereo != null) && (stereo.length() > 0)) { sb.append(stereo).append(" "); } if (showVisibility) { String visibility = NotationUtilityUml .generateVisibility2(modelElement); if (visibility != null && visibility.length() > 0) { sb.append(visibility); } } if ((name != null) && (name.length() > 0)) { sb.append(name).append(" "); } if ((multiplicity != null) && (multiplicity.length() > 0) && showMultiplicity) { sb.append("[").append(multiplicity).append("]").append(" "); } if ((type != null) && (type.length() > 0) /* * The "show types" defaults to TRUE, to stay compatible * with older ArgoUML versions that did not have this * setting: */ && showTypes) { sb.append(": ").append(type).append(" "); } if (showInitialValues) { Object iv = Model.getFacade().getInitialValue(modelElement); if (iv != null) { String initialValue = (String) Model.getFacade().getBody(iv); if (initialValue != null && initialValue.length() > 0) { sb.append(" = ").append(initialValue).append(" "); } } } if (showProperties) { String changeableKind = ""; if (Model.getFacade().isReadOnly(modelElement)) { changeableKind = "frozen"; } if (Model.getFacade().getChangeability(modelElement) != null) { if (Model.getChangeableKind().getAddOnly().equals( Model.getFacade().getChangeability(modelElement))) { changeableKind = "addOnly"; } } StringBuilder properties = new StringBuilder(); if (changeableKind.length() > 0) { properties.append("{ ").append(changeableKind).append(" }"); } if (properties.length() > 0) { sb.append(properties); } } return sb.toString().trim(); } catch (InvalidElementException e) { // The element was deleted while we were processing it return ""; } } private static String generateMultiplicity(Object m) { if (m == null || "1".equals(Model.getFacade().toString(m))) { return ""; } return Model.getFacade().toString(m); } }