blob: 3d91efa7de2e69c986965732dd25a3002aa34600 [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2012 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:
* CEA LIST - Initial API and implementation
* Nicolas FAUVERGUE (ALL4TEC) nicolas.fauvergue@all4tec.net - Bug 496905
*
*****************************************************************************/
package org.eclipse.papyrus.sysml.service.types.utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;
import org.eclipse.gmf.runtime.notation.View;
import org.eclipse.papyrus.sysml.blocks.Block;
import org.eclipse.papyrus.sysml.blocks.NestedConnectorEnd;
import org.eclipse.papyrus.uml.internationalization.utils.utils.UMLLabelInternationalization;
import org.eclipse.uml2.uml.ConnectableElement;
import org.eclipse.uml2.uml.Connector;
import org.eclipse.uml2.uml.ConnectorEnd;
import org.eclipse.uml2.uml.NamedElement;
import org.eclipse.uml2.uml.Port;
import org.eclipse.uml2.uml.Property;
import org.eclipse.uml2.uml.Stereotype;
import org.eclipse.uml2.uml.StructuredClassifier;
import org.eclipse.uml2.uml.Type;
import org.eclipse.uml2.uml.TypedElement;
import org.eclipse.uml2.uml.util.UMLUtil;
/**
* Utility class for Connector edit helpers.
*/
public class ConnectorUtils extends org.eclipse.papyrus.uml.service.types.utils.ConnectorUtils {
/**
* the key used to store the nested path as parameter of the request. The value will be a List of {@link Property}
*/
public static String NESTED_CONNECTOR_END_PATH = "connectorEndPath"; //$NON-NLS-1$
/** the separator used in the role path to distinguish the part. */
public static final String PART_SEPARATOR = "."; //$NON-NLS-1$
/** allow to know if a string contains others characters than a-z A-Z 0-9 and _. */
public static final String HAS_NO_WORD_CHAR_REGEX = "\\W+";
/** String used to delimit a name with contains special chars. */
public static final String STRING_DELIMITER = "\'";
/**
* Gets the encapsulated container.
*
* @param view
* the view
* @return the nearest encapsulated structure container or null
*/
public View getEncapsulatedContainer(View view) {
View encapsulatedContainer = null;
for (View containerView : getStructureContainers(view)) {
if (view == containerView) {
continue;
}
if ((view.getElement() instanceof Port) && (containerView.getChildren().contains(view))) {
continue;
}
StructuredClassifier structuredClassifier = getStructuredClassifier(containerView);
Block block = UMLUtil.getStereotypeApplication(structuredClassifier, Block.class);
if (block.isEncapsulated()) {
encapsulatedContainer = containerView;
break;
}
}
return encapsulatedContainer;
}
/**
* Test if an encapsulation crossing is required to connect the checked end to the opposite end.
*
* @param checkedEnd
* the checked end view.
* @param oppositeEnd
* the opposite end view.
* @return true if the gesture break encapsulation rule.
*/
public boolean isCrossingEncapsulation(View checkedEnd, View oppositeEnd) {
boolean isCrossingEncapsulation = false;
View encapsulatedContainer = new ConnectorUtils().getEncapsulatedContainer(checkedEnd);
if (encapsulatedContainer != null) {
View containerView = new ConnectorUtils().deduceViewContainer(checkedEnd, oppositeEnd);
List<View> containers = new ConnectorUtils().getStructureContainers(checkedEnd);
if (containers.indexOf(encapsulatedContainer) < containers.indexOf(containerView)) {
isCrossingEncapsulation = true;
}
}
return isCrossingEncapsulation;
}
/**
* Get the path of structure views crossed by the checked end.
*
* @param checkedEnd
* the checked end view.
* @param oppositeEnd
* the opposite end view.
* @return the list of crossed structure views.
*/
public List<View> getNestedPath(View checkedEnd, View oppositeEnd) {
List<View> isNestedConnectableElement = new ArrayList<View>();
View nearestContainer = new ConnectorUtils().getNearestStructureContainer(checkedEnd);
if (nearestContainer != null) {
View containerView = new ConnectorUtils().deduceViewContainer(checkedEnd, oppositeEnd);
List<View> containers = new ConnectorUtils().getStructureContainers(checkedEnd);
if (containers.indexOf(nearestContainer) < containers.indexOf(containerView)) {
isNestedConnectableElement = containers.subList(containers.indexOf(nearestContainer), containers.indexOf(containerView));
}
}
// nested path is taken from the top block to the deepest property ==> collection must be reverted
Collections.reverse(isNestedConnectableElement);
return isNestedConnectableElement;
}
/**
* Get the path of structure views crossed by the checked end.
*
* @param checkedEnd
* the checked end view.
* @param oppositeEnd
* the opposite end view.
* @return the list of crossed structure views.
*/
public List<Property> getNestedPropertyPath(View checkedEnd, View oppositeEnd) {
List<Property> nestedPropertyPath = new ArrayList<Property>();
List<View> nestedPath = getNestedPath(checkedEnd, oppositeEnd);
for (View view : nestedPath) {
if ((view.getElement() != null) && (view.getElement() instanceof Property)) {
nestedPropertyPath.add((Property) view.getElement());
}
}
// if end is a port, and the list is not empty, add the property from the check view in the list
if (!nestedPropertyPath.isEmpty() && checkedEnd.getElement() instanceof Port) {
Property partWithPort = getPartWithPort(checkedEnd, oppositeEnd);
if (partWithPort != null) {
nestedPropertyPath.add(partWithPort);
}
}
return nestedPropertyPath;
}
/**
* Test if the checked end is nested (means it cross StructuredClassifier borders).
*
* @param checkedEnd
* the checked end view.
* @param oppositeEnd
* the opposite end view.
* @return true if the checked end is nested.
*/
public boolean isNestedConnectableElement(View checkedEnd, View oppositeEnd) {
return !(getNestedPath(checkedEnd, oppositeEnd).isEmpty());
}
/**
* Gets the nearest structure container.
*
* @param view
* the view
* @return the nearest structure container or null
*/
public View getNearestStructureContainer(View view) {
View nearestStructureContainer = null;
for (View containerView : getStructureContainers(view)) {
if (view == containerView) {
continue;
}
if ((view.getElement() instanceof Port) && (containerView.getChildren().contains(view))) {
continue;
}
nearestStructureContainer = containerView;
break;
}
return nearestStructureContainer;
}
/**
* Gets the role path.
*
* @param end
* the connector end
* @return the role path
* the path for the role of the connector end (without using label provider)
*/
public static final String getRolePath(final ConnectorEnd end) {
final NestedConnectorEnd nestedConnectorEnd = UMLUtil.getStereotypeApplication(end, NestedConnectorEnd.class);
final ConnectableElement role = end.getRole();
final StringBuilder rolePath = new StringBuilder();
if (role != null) {
if (nestedConnectorEnd != null) {
final List<Property> properties = nestedConnectorEnd.getPropertyPath();
for (final Property current : properties) {
rolePath.append(getNameWithQuotes(current));
rolePath.append(ConnectorUtils.PART_SEPARATOR);
}
} else {
// when the stereotype is applied, the Property for partWithPort is included in the stereotype#path
final Property partWithPort = end.getPartWithPort();
if (partWithPort != null) {
rolePath.append(getNameWithQuotes(partWithPort));
rolePath.append(ConnectorUtils.PART_SEPARATOR);
}
}
rolePath.append(getNameWithQuotes(role));
}
return rolePath.toString();
}
/**
* Gets the name with quotes.
*
* @param property
* a property
* @return the name with quotes
* the property name with name delimiter if it is required
*/
public static final String getNameWithQuotes(final NamedElement property) {
final String partName = UMLLabelInternationalization.getInstance().getLabel(property);
final StringBuffer partNameBuffer = new StringBuffer();
final Pattern pattern = Pattern.compile(ConnectorUtils.HAS_NO_WORD_CHAR_REGEX);
final Matcher matcher = pattern.matcher(partName);
boolean mustHaveQuote = false;
while (matcher.find() && !mustHaveQuote) {
mustHaveQuote = true;
}
if (mustHaveQuote) {
partNameBuffer.append(ConnectorUtils.STRING_DELIMITER);
partNameBuffer.append(partName);
partNameBuffer.append(ConnectorUtils.STRING_DELIMITER);
} else {
partNameBuffer.append(partName);
}
return partNameBuffer.toString();
}
/**
* Checks if is crossing encapsulation.
*
* @param nestedPath
* the nested path
* @return true, if is crossing encapsulation <code>true</code> if we are breaking encapsulation (see SysML rules in SysML Standard 1.2, p.44):
* isEncapsulated: Boolean [0..1] If true, then the block is treated as a black box; a part typed by this black box can only be connected
* via its ports or directly to its outer boundary. If false, or if a value is not present, then connections can be established to
* elements of its internal structure via deep-nested connector ends.
*/
public static final boolean isCrossingEncapuslation(final List<Property> nestedPath) {
for (final Property current : nestedPath) {
final Type type = current.getType();
if (type != null) {
final Block block = UMLUtil.getStereotypeApplication(type, Block.class);
if (block != null && block.isEncapsulated()) {
return true;
}
}
}
return false;
}
/**
* Can display existing connector between views according to nested paths.
*
* @param connector
* a connector existing in the model
* @param sourceView
* a potential source for this connector
* @param targetView
* a potential target for this connector
* @return true, if successful <code>true</code> if displaying the existing connector between this source and this target view is correct
*/
public static boolean canDisplayExistingConnectorBetweenViewsAccordingToNestedPaths(final Connector connector, final View sourceView, final View targetView) {
ConnectorUtils utils = new ConnectorUtils();
final List<Property> sourcePath = utils.getNestedPropertyPath(sourceView, targetView);
final List<Property> targetPath = utils.getNestedPropertyPath(targetView, sourceView);
boolean hasWantedPath = true;
for (final ConnectorEnd end : connector.getEnds()) {
if (sourceView != null && end.getRole() == sourceView.getElement()) {
hasWantedPath = hasWantedPath && haveSamePath(sourcePath, end);
} else if (targetView != null && end.getRole() == targetView.getElement()) {
hasWantedPath = hasWantedPath && haveSamePath(targetPath, end);
}
}
return hasWantedPath;
}
/**
* Have same path.
*
* @param wantedPath
* the wanted nested path for the end
* @param end
* an end
* @return true, if successful
* true if the end has as nested path THE wanted path
*/
protected static boolean haveSamePath(final List<Property> wantedPath, final ConnectorEnd end) {
Stereotype ste = end.getAppliedStereotype("SysML::Blocks::NestedConnectorEnd");//$NON-NLS-1$
if (ste != null) {
final NestedConnectorEnd nestedConnectorEnd = (NestedConnectorEnd) end.getStereotypeApplication(ste);
return nestedConnectorEnd.getPropertyPath().equals(wantedPath);
} else {
return wantedPath.isEmpty();
}
}
/**
* Can display existing connector between views according to partWithPort.
*
* @param connector
* the connector
* @param sourceView
* the source view
* @param targetView
* the target view
* @return true, if successful
*/
public boolean canDisplayExistingConnectorBetweenViewsAccordingToPartWithPort(final Connector connector, final View sourceView, final View targetView) {
Property partWithPort = getPartWithPortFromConnector(connector);
if (partWithPort != null) {
String partWithPortName = partWithPort.getName();
Type partWithPortType = partWithPort.getType();
EObject sourceContainer = ((View) sourceView.eContainer()).getElement();
EObject targetContainer = ((View) targetView.eContainer()).getElement();
boolean sameSourceType = false;
if (sourceContainer instanceof TypedElement) {
sameSourceType = partWithPortType.conformsTo(((TypedElement) sourceContainer).getType());
}
boolean sameSourceName = partWithPortName.equals(((NamedElement) sourceContainer).getName());
boolean sameTargetType = false;
if (targetContainer instanceof TypedElement) {
sameTargetType = partWithPortType.conformsTo(((TypedElement) targetContainer).getType());
}
boolean sametargetName = partWithPortName.equals(((NamedElement) targetContainer).getName());
return (sameSourceType && sameSourceName) || (sameTargetType && sametargetName);
}
return true;
}
/**
* Gets the partWithPort from the connector.
*
* @param connector
* the connector
* @return the part with port from connector
*/
public Property getPartWithPortFromConnector(final Connector connector) {
if (connector != null && connector.getEnds() != null) {
for (final ConnectorEnd end : connector.getEnds()) {
Property partWithPort = end.getPartWithPort();
if (partWithPort != null) {
return partWithPort;
}
}
}
return null;
}
/**
* Test if the relationship creation is allowed.
*
* @param source
* the relationship source can be null
* @param target
* the relationship target can be null
* @param sourceView
* the relationship graphical source can be null
* @param targetView
* the relationship graphical target can be null
* @return true if the creation is allowed
*/
public static boolean canCreate(EObject source, EObject target, View sourceView, View targetView) {
if ((source != null) && !(source instanceof ConnectableElement)) {
return false;
}
if ((target != null) && !(target instanceof ConnectableElement)) {
return false;
}
if ((sourceView != null) && (targetView != null)) {
// Allow to create a self connector on a view
// if(sourceView == targetView) {
// return false;
// }
// Cannot create a connector from a view representing a Part to its own Port (or the opposite)
if ((sourceView.getChildren().contains(targetView)) || (targetView.getChildren().contains(sourceView))) {
return false;
}
// Cannot connect a Part to one of its (possibly indirect) containment, must connect to one of its Port.
if (new ConnectorUtils().getStructureContainers(sourceView).contains(targetView) || new ConnectorUtils().getStructureContainers(targetView).contains(sourceView)) {
return false;
}
}
return true;
}
/**
* Filter connectors that have this property in their <NestedConnectorEnd> property path.
*
* @param connectors
* @param part
* @return connectors that have this property in their <NestedConnectorEnd> property path.
*/
public static List<Connector> filterConnectorByPropertyInNestedConnectorEnd(List<Connector> connectors, Property part) {
List<Connector> res = new ArrayList<Connector>();
for (Connector connector : connectors) {
EList<ConnectorEnd> ends = connector.getEnds();
for (ConnectorEnd connectorEnd : ends) {
NestedConnectorEnd stereotypeApplication = UMLUtil.getStereotypeApplication(connectorEnd, NestedConnectorEnd.class);
if (stereotypeApplication != null) {
EList<Property> propertyPath = stereotypeApplication.getPropertyPath();
for (Property property : propertyPath) {
if (property.equals(part)) {
res.add(connector);
}
}
}
}
}
return res;
}
/**
* Filter connectors that have this property in their <NestedConnectorEnd> property path.
*
* @param inverseReferences
* All the references towards part
* @param part
*
* @return connectors that have this property in their <NestedConnectorEnd> property path.
*/
public static List<Connector> filterConnectorByPropertyInNestedConnectorEnd(Collection<Setting> inverseReferences, Property part) {
Set<Connector> result = new HashSet<Connector>();
for (Setting setting : inverseReferences) {
EObject referent = setting.getEObject();
if (referent instanceof NestedConnectorEnd) {
NestedConnectorEnd stereotypeApplication = (NestedConnectorEnd) referent;
if (stereotypeApplication.getPropertyPath().contains(part)) {
ConnectorEnd end = stereotypeApplication.getBase_ConnectorEnd();
if (end != null && end.getOwner() instanceof Connector) {
result.add((Connector) end.getOwner());
}
}
}
}
return new ArrayList<Connector>(result);
}
}