/***************************************************************************** * Copyright (c) 2010 CEA LIST. * * 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: * Remi Schnekenburger (CEA LIST) remi.schnekenburger@cea.fr - Initial API and implementation *****************************************************************************/ package org.eclipse.papyrus.uml.diagram.common.service.palette; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.commands.operations.IUndoableOperation; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.transaction.Transaction; import org.eclipse.emf.workspace.AbstractEMFOperation; import org.eclipse.gef.EditPart; import org.eclipse.gef.EditPartViewer; import org.eclipse.gef.palette.CombinedTemplateCreationEntry; import org.eclipse.gmf.runtime.common.core.command.CompositeCommand; import org.eclipse.gmf.runtime.common.core.util.StringStatics; import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart; import org.eclipse.gmf.runtime.diagram.ui.parts.DiagramGraphicalViewer; import org.eclipse.gmf.runtime.diagram.ui.util.EditPartUtil; import org.eclipse.gmf.runtime.notation.View; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.papyrus.uml.diagram.common.Activator; import org.eclipse.papyrus.uml.diagram.common.helper.AssociationHelper; import org.eclipse.papyrus.uml.diagram.common.part.PaletteUtil; import org.eclipse.papyrus.uml.tools.utils.MultiplicityElementUtil; import org.eclipse.swt.SWT; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.uml2.uml.AggregationKind; import org.eclipse.uml2.uml.Association; import org.eclipse.uml2.uml.Profile; import org.eclipse.uml2.uml.Property; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * Aspect action that modifies association end on creation */ public class AssociationEndPostAction extends ModelPostAction { /** * list of property configurations, identified by their positions in the * association end list */ protected List<PropertyEndConfiguration> configurations; /** entry proxy "parent" of this action when configuring the action */ protected IPaletteEntryProxy entryProxy; /** list of applied profiles */ protected List<Profile> appliedProfiles; /** viewer for the attributes to initialize */ protected TableViewer attributeViewer; /** * this attribute caches the value of the metaclass linked to the creation * entry (performance optimization) */ protected EClass metaclass = null; /** path to the checked box image */ protected final static String ICON_CHECKED = "/icons/complete_tsk.gif"; /** path to the unchecked box image */ protected final static String ICON_UNCHECKED = "/icons/incomplete_tsk.gif"; /** separator used to serialize lists */ protected static final String SEPARATOR = ",,"; /** node name for the configuration of the association end */ protected static final String ASSOCIATION_END_NODE_NAME = "associationEnd"; /** attribute name for the index of the association end */ protected static final String INDEX_ATTRIBUTE_NAME = "index"; /** attribute name for the name of the association end */ protected static final String NAME_ATTRIBUTE_NAME = "name"; /** node name for the aggregation configuration of the association end */ protected static final String AGGREGATION_NODE_NAME = "aggregation"; /** node name for the value(s) */ protected static final String VALUE_ATTRIBUTE_NAME = "value"; /** node name for the multiplicity configuration of the association end */ protected static final String MULTIPLICITY_NODE_NAME = "multiplicity"; /** node name for the navigation configuration of the association end */ protected static final String NAVIGABLE_NODE_NAME = "navigable"; /** node name for the owner configuration of the association end */ protected static final String OWNER_NODE_NAME = "owner"; /** value of the owned by class configuration element */ protected static final String CLASS_OWNER = "Class"; /** value of the "navigability" configuration element */ protected static final String NAVIGABLE_YES = "Yes"; /** list of composites */ protected List<PropertyEndComposite> configurationComposites = new ArrayList<PropertyEndComposite>(); /** * Constructor. */ public AssociationEndPostAction() { configurations = new ArrayList<PropertyEndConfiguration>(); } /** * @{inheritDoc */ @Override public void init(Node configurationNode, IAspectActionProvider factory) { super.init(configurationNode, factory); if(configurationNode == null) { // creates 2 default configuration property PropertyEndConfiguration sourceConfiguration = new PropertyEndConfiguration(0, "source"); PropertyEndConfiguration targetConfiguration = new PropertyEndConfiguration(1, "target"); configurations.add(sourceConfiguration); configurations.add(targetConfiguration); return; } NodeList childNodes = configurationNode.getChildNodes(); for(int i = 0; i < childNodes.getLength(); i++) { Node featureNode = childNodes.item(i); if(ASSOCIATION_END_NODE_NAME.equals(featureNode.getNodeName())) { Node indexNode = featureNode.getAttributes().getNamedItem(INDEX_ATTRIBUTE_NAME); Node nameNode = featureNode.getAttributes().getNamedItem(NAME_ATTRIBUTE_NAME); if(indexNode != null && nameNode != null) { Integer index = Integer.parseInt(indexNode.getNodeValue()); PropertyEndConfiguration configuration = new PropertyEndConfiguration(index, nameNode.getNodeValue()); // parse sub nodes (aggregation, navigation, etc.) NodeList subNodes = featureNode.getChildNodes(); for(int j = 0; j < subNodes.getLength(); j++) { Node subNode = subNodes.item(j); String subNodeName = subNode.getNodeName(); if(AGGREGATION_NODE_NAME.equals(subNodeName)) { Node valueNode = subNode.getAttributes().getNamedItem(VALUE_ATTRIBUTE_NAME); if(valueNode != null) { configuration.setAggregationKind(valueNode.getNodeValue()); } } else if(MULTIPLICITY_NODE_NAME.equals(subNodeName)) { Node valueNode = subNode.getAttributes().getNamedItem(VALUE_ATTRIBUTE_NAME); if(valueNode != null) { configuration.setMultiplicity(valueNode.getNodeValue()); } } else if(NAVIGABLE_NODE_NAME.equals(subNodeName)) { Node valueNode = subNode.getAttributes().getNamedItem(VALUE_ATTRIBUTE_NAME); if(valueNode != null) { configuration.setNavigation(valueNode.getNodeValue()); } } else if(OWNER_NODE_NAME.equals(subNodeName)) { Node valueNode = subNode.getAttributes().getNamedItem(VALUE_ATTRIBUTE_NAME); if(valueNode != null) { configuration.setOwner(valueNode.getNodeValue()); } } } configurations.add(configuration); } else { Activator.log.error("Impossible to parse the configuration node for semantic post action", null); } } } } /** * {@inheritDoc} */ public void run(EditPart editPart) { final CompositeCommand compositeCommand = new CompositeCommand("Modify Association End"); EObject objectToEdit = ((View)editPart.getModel()).getElement(); // object to edit should be an association... if(objectToEdit instanceof Association) { for(PropertyEndConfiguration configuration : configurations) { // retrieve Property... more tests to do!! Property property = ((Association)objectToEdit).getMemberEnds().get(configuration.getIndex()); if(!("".equals(configuration.getOwner()))) { // change the owner to the specified one IUndoableOperation operation = AssociationHelper.createSetOwnerCommand((Association)objectToEdit, property, CLASS_OWNER.equals(configuration.getOwner())); if(operation != null) { compositeCommand.compose(operation); } } if(!("".equals(configuration.getAggregationKind()))) { // change the aggregation kind AggregationKind aggregationKind = AggregationKind.get(configuration.getAggregationKind()); compositeCommand.compose(AssociationHelper.createSetAggregationCommand(property, aggregationKind)); } if(!("".equals(configuration.getMultiplicity()))) { // change the aggregation kind String multiplicity = configuration.getMultiplicity(); try { int[] values = MultiplicityElementUtil.parseMultiplicity(multiplicity); if(values.length == 2) { compositeCommand.compose(AssociationHelper.createSetMultiplicityCommand(property, values[0], values[1])); } } catch (NumberFormatException e) { Activator.log.error("Multiplicity [" + multiplicity + "] can not be parsed", e); } } if(!("".equals(configuration.getNavigation()))) { String navigation = configuration.getNavigation(); boolean isNavigable = NAVIGABLE_YES.equals(navigation); compositeCommand.compose(AssociationHelper.createSetNavigableCommand(((Association)objectToEdit), property, isNavigable)); } } } compositeCommand.reduce(); if(compositeCommand.canExecute()) { boolean isActivating = true; Map<String, Boolean> options = null; // use the viewer to determine if we are still initializing the // diagram // do not use the DiagramEditPart.isActivating since // ConnectionEditPart's // parent will not be a diagram edit part EditPartViewer viewer = editPart.getViewer(); if(viewer instanceof DiagramGraphicalViewer) { isActivating = ((DiagramGraphicalViewer)viewer).isInitializing(); } if(isActivating || !EditPartUtil.isWriteTransactionInProgress((IGraphicalEditPart)editPart, false, false)) { options = Collections.singletonMap(Transaction.OPTION_UNPROTECTED, Boolean.TRUE); } AbstractEMFOperation operation = new AbstractEMFOperation(((IGraphicalEditPart)editPart).getEditingDomain(), StringStatics.BLANK, options) { protected IStatus doExecute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { compositeCommand.execute(monitor, info); return Status.OK_STATUS; } }; try { operation.execute(new NullProgressMonitor(), null); } catch (ExecutionException e) { Activator.log.error(e); } } } /** * @{inheritDoc */ public Control createConfigurationComposite(Composite parent, IPaletteEntryProxy entryProxy, List<Profile> appliedProfiles) { this.appliedProfiles = appliedProfiles; this.entryProxy = entryProxy; // retrieve tool metaclass if(entryProxy.getEntry() instanceof CombinedTemplateCreationEntry) { metaclass = PaletteUtil.getToolMetaclass((CombinedTemplateCreationEntry)entryProxy.getEntry()); } Composite mainComposite = new Composite(parent, SWT.BORDER); GridLayout layout = new GridLayout(2, true); mainComposite.setLayout(layout); Label titleLabel = new Label(mainComposite, SWT.NONE); titleLabel.setText("Association end properties"); GridData data = new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1); titleLabel.setLayoutData(data); for(PropertyEndConfiguration configuration : configurations) { PropertyEndComposite composite = new PropertyEndComposite(configuration); composite.createComposite(mainComposite); configurationComposites.add(composite); } return mainComposite; } /** * @{inheritDoc */ public void save(Node parentNode) { if(!(parentNode instanceof Element)) { Activator.log.error("parent node is not an Element", null); return; } else { // a sub node should be created for each configuration for(PropertyEndConfiguration configuration : configurations) { Element associationEndNode = ((Element)parentNode).getOwnerDocument().createElement(ASSOCIATION_END_NODE_NAME); associationEndNode.setAttribute(INDEX_ATTRIBUTE_NAME, configuration.getIndex() + ""); associationEndNode.setAttribute(NAME_ATTRIBUTE_NAME, configuration.getName()); // save child nodes, if required if(!"".equals(configuration.getAggregationKind())) { createChildNode(associationEndNode, AGGREGATION_NODE_NAME, configuration.getAggregationKind()); } if(!"".equals(configuration.getMultiplicity())) { createChildNode(associationEndNode, MULTIPLICITY_NODE_NAME, configuration.getMultiplicity()); } if(!"".equals(configuration.getNavigation())) { createChildNode(associationEndNode, NAVIGABLE_NODE_NAME, configuration.getNavigation()); } if(!"".equals(configuration.getOwner())) { createChildNode(associationEndNode, OWNER_NODE_NAME, configuration.getOwner()); } ((Element)parentNode).appendChild(associationEndNode); } } } /** * Creates a child node to the given element * * @param parent * the element owner of the new child node * @param tagName * the name of the child node * @param value * the value of the "value" attribute node */ protected void createChildNode(Element parent, String tagName, String value) { Element childNode = parent.getOwnerDocument().createElement(tagName); childNode.setAttribute(VALUE_ATTRIBUTE_NAME, value); parent.appendChild(childNode); } /** * Composite to configure the property end */ protected class PropertyEndComposite { /** list of items for the aggregation combo */ final protected List<String> aggregationItems = Arrays.asList("", AggregationKind.NONE_LITERAL.getName(), AggregationKind.COMPOSITE_LITERAL.getName(), AggregationKind.SHARED_LITERAL.getName()); /** list of items for the aggregation combo */ final protected List<String> multiplicityItems = Arrays.asList("", "0..1", "1..1", "0..*", "1..*"); /** List of items for navigation selection */ final protected List<String> navigationItems = Arrays.asList("", NAVIGABLE_YES, "No"); /** List of items for owner selection */ final protected List<String> ownerItems = Arrays.asList("", CLASS_OWNER, "Association"); /** property configuration */ final protected PropertyEndConfiguration configuration; /** label for this composite */ final protected String label; /** Combo for aggregation kind selection */ protected Combo aggregationCombo; /** Button to select class as owner of the association end */ protected Button ownerClassButton; /** Button to select association as owner of the association end */ protected Button ownerAssociationButton; /** Combo for navigation selection */ protected Combo navigationCombo; /** Combo for multiplicity selection */ protected Combo multiplicityCombo; /** Combo for owner selection */ protected Combo ownerCombo; /** * Constructor. * * @param index * the index of the property in the association ends list */ public PropertyEndComposite(PropertyEndConfiguration configuration) { this.configuration = configuration; label = configuration.getName(); } /** * Creates the composite for configuration * * @param parent * the composite in which sub-composite should be created */ public void createComposite(Composite parent) { Group mainComposite = new Group(parent, SWT.NONE); mainComposite.setText("Property end " + this.label); GridLayout layout = new GridLayout(3, false); mainComposite.setLayout(layout); GridData data = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1); mainComposite.setLayoutData(data); // aggregation elements Label aggregationLabel = new Label(mainComposite, SWT.NONE); aggregationLabel.setText("Aggregation"); aggregationLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1)); aggregationCombo = new Combo(mainComposite, SWT.READ_ONLY); aggregationCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1)); aggregationCombo.setItems(aggregationItems.toArray(new String[]{})); aggregationCombo.addFocusListener(new FocusListener() { public void focusLost(FocusEvent e) { configuration.setAggregationKind(aggregationCombo.getText()); } public void focusGained(FocusEvent e) { } }); // owner elements Label ownerLabel = new Label(mainComposite, SWT.NONE); ownerLabel.setText("Owner"); ownerLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1)); ownerCombo = new Combo(mainComposite, SWT.READ_ONLY); ownerCombo.setItems(ownerItems.toArray(new String[]{})); ownerCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1)); ownerCombo.addSelectionListener(new SelectionListener() { /** * @{inheritDoc */ public void widgetSelected(SelectionEvent e) { String value = ((Combo)e.widget).getText(); configuration.setOwner(value); } /** * @{inheritDoc */ public void widgetDefaultSelected(SelectionEvent e) { } }); // navigation elements Label navigationLabel = new Label(mainComposite, SWT.NONE); navigationLabel.setText("Navigable"); navigationLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1)); navigationCombo = new Combo(mainComposite, SWT.READ_ONLY); navigationCombo.setItems(navigationItems.toArray(new String[]{})); navigationCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1)); navigationCombo.addSelectionListener(new SelectionListener() { /** * @{inheritDoc */ public void widgetSelected(SelectionEvent e) { String value = ((Combo)e.widget).getText(); configuration.setNavigation(value); } /** * @{inheritDoc */ public void widgetDefaultSelected(SelectionEvent e) { } }); // multiplicity elements Label multiplicityLabel = new Label(mainComposite, SWT.NONE); multiplicityLabel.setText("Multiplicity"); multiplicityLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1)); multiplicityCombo = new Combo(mainComposite, SWT.BORDER); multiplicityCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1)); multiplicityCombo.setItems(multiplicityItems.toArray(new String[]{})); multiplicityCombo.addFocusListener(new FocusListener() { /** * @{inheritDoc */ public void focusLost(FocusEvent e) { // check validity. open a window to ask if we should stay on // multiplicity combo or left. String newValue = multiplicityCombo.getText(); if(isMultiplicityValid(newValue.trim())) { configuration.setMultiplicity(newValue.trim()); } else { boolean leaveCombo = MessageDialog.openQuestion(multiplicityCombo.getShell(), "Incorrect multiplicity value", "The text [" + newValue + "] is not a valid value.\nDo you really want to leave the combo, losing your modification on multiplicity value?"); if(!leaveCombo) { multiplicityCombo.setFocus(); multiplicityCombo.setText(newValue); } else { // restore old value multiplicityCombo.setText(configuration.getMultiplicity()); } } } /** * @{inheritDoc */ public void focusGained(FocusEvent e) { } }); update(); } /** * update the values in the various composites, given the currently hold * values */ public void update() { navigationCombo.select(navigationItems.indexOf(configuration.getNavigation())); aggregationCombo.select(aggregationItems.indexOf(configuration.getAggregationKind())); ownerCombo.select(ownerItems.indexOf(configuration.getOwner())); multiplicityCombo.setText(configuration.getMultiplicity()); } /** * Checks if the multiplicity is valid * * @param newValue * the value to check * @return true if the String represents a valid multiplicity */ private boolean isMultiplicityValid(String newValue) { // checks if is only a string if(newValue.equals("")) { return true; } try { int[] values = MultiplicityElementUtil.parseMultiplicity(newValue); if(values.length == 2) { int lower = values[0]; int upper = values[1]; // returns true if upper = *, otherwise, returns true if // upper > lower return (upper == -1) ? true : upper >= lower; } return false; } catch (NumberFormatException e) { return false; } } } protected class PropertyEndConfiguration { /** current value of the aggregation kind attribute */ protected String aggregationKind = ""; /** current value of the owner */ protected String owner = ""; /** current value for the navigation */ protected String navigation = ""; /** current multiplicity */ protected String multiplicity = ""; /** index of the property end */ protected final int index; /** name of the property end */ protected final String name; /** * Returns the name of this configuration (the name of the property end) * * @return the name of this configuration (the name of the property end) */ public String getName() { return name; } /** * Returns the index of this configuration (the index of the property * end) * * @return the index of this configuration (the index of the property * end */ public int getIndex() { return index; } /** * Constructor. * * @param index * the index of the property end */ public PropertyEndConfiguration(int index, String name) { this.index = index; this.name = name; } /** * Returns the current value of aggregationKind * * @return the current value of aggregationKind */ public String getAggregationKind() { return aggregationKind; } /** * Sets the current value of aggregationKind * * @param aggregationKind * the aggregationKind to set */ public void setAggregationKind(String aggregationKind) { this.aggregationKind = aggregationKind; } /** * Returns the current value of owner * * @return the current value of owner */ public String getOwner() { return owner; } /** * Sets the current value of owner * * @param owner * the owner to set */ public void setOwner(String owner) { this.owner = owner; } /** * Returns the current value of navigation * * @return the current value of navigation */ public String getNavigation() { return navigation; } /** * Sets the current value of navigation * * @param navigation * the navigation to set */ public void setNavigation(String navigation) { this.navigation = navigation; } /** * Returns the current value of multiplicity * * @return the current value of multiplicity */ public String getMultiplicity() { return multiplicity; } /** * Sets the current value of multiplicity * * @param multiplicity * the multiplicity to set */ public void setMultiplicity(String multiplicity) { this.multiplicity = multiplicity; } } }