/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.gui.workflow.editor.handlers;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.ArrayNode;
import org.codehaus.jackson.node.ObjectNode;
import org.eclipse.draw2d.FigureCanvas;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.MenuItem;
import de.rcenvironment.core.component.model.api.ComponentDescription;
import de.rcenvironment.core.component.model.api.ComponentSize;
import de.rcenvironment.core.component.model.endpoint.api.EndpointDescription;
import de.rcenvironment.core.component.model.endpoint.api.EndpointGroupDescription;
import de.rcenvironment.core.component.workflow.model.api.Connection;
import de.rcenvironment.core.component.workflow.model.api.Location;
import de.rcenvironment.core.component.workflow.model.api.WorkflowDescription;
import de.rcenvironment.core.component.workflow.model.api.WorkflowDescriptionPersistenceHandler;
import de.rcenvironment.core.component.workflow.model.api.WorkflowLabel;
import de.rcenvironment.core.component.workflow.model.api.WorkflowNode;
import de.rcenvironment.core.gui.utils.common.ClipboardHelper;
import de.rcenvironment.core.gui.workflow.ConnectionUtils;
import de.rcenvironment.core.gui.workflow.editor.WorkflowEditor;
import de.rcenvironment.core.gui.workflow.editor.commands.WorkflowNodeLabelConnectionCreateCommand;
import de.rcenvironment.core.gui.workflow.parts.WorkflowEditorEditPartFactory;
import de.rcenvironment.core.gui.workflow.parts.WorkflowLabelPart;
import de.rcenvironment.core.gui.workflow.parts.WorkflowNodePart;
import de.rcenvironment.core.utils.common.JsonUtils;
import de.rcenvironment.core.utils.common.StringUtils;
/**
* Pasting {@link WorkflowNode}s, {@link Connection}s, {@link EndpointDescription}s and
* {@link WorkflowLabel}s to the {@link WorkflowEditor} .
*
*
* @author Doreen Seider
* @author Oliver Seebach
* @author Sascha Zur
* @author Marc Stammerjohann
* @author David Scholz
*/
// TODO fix class name, it is not longer only a paste handler for WorkflowNodes
public class WorkflowNodePasteHandler extends AbstractWorkflowNodeEditHandler {
private static final ObjectMapper JSON_OBJECT_MAPPER = JsonUtils.getDefaultObjectMapper();
private final Map<String, String> nameMapping = new HashMap<String, String>();
// mapping between old identifier and new workflow node !
private final Map<WorkflowNode, WorkflowNode> nodeMapping = new HashMap<WorkflowNode, WorkflowNode>();
private final Map<String, Map<String, EndpointDescription>> endpointIDMapping = new HashMap<String, Map<String, EndpointDescription>>();
private final Map<WorkflowNode, Rectangle> newNodeAndLocationMapping = new HashMap<>();
private int nodeConstraintPositionCounter = 0;
private int labelConstraintPositionCounter = 0;
private final int offset = 20;
private int componentSizeOffset = 0;
private boolean hasConnections = false;
private int nodeMinX = Integer.MAX_VALUE;
private int nodeMinY = Integer.MAX_VALUE;
private int labelMinX = Integer.MAX_VALUE;
private int labelMinY = Integer.MAX_VALUE;
private Point editorOffsetPoint = null;
private boolean pasteTriggeredByMouse;
@Override
void edit() {
nameMapping.clear();
nodeConstraintPositionCounter = 0;
hasConnections = false;
labelConstraintPositionCounter = 0;
List<WorkflowNode> workflowNodesToCreate = new LinkedList<>();
List<WorkflowNodePart> pastedWorkflowNodeParts = new LinkedList<>();
List<WorkflowLabel> workflowLabelsToCreate = new LinkedList<>();
List<Connection> connectionsToCreate = new LinkedList<>();
List<Rectangle> nodeConstraintsToCreate = new LinkedList<>();
List<Rectangle> labelConstraintsToCreate = new LinkedList<>();
pasteTriggeredByMouse = false;
// determines whether the pasting was triggered via hotkey ctrl+v or via mouse (
// editor's context menu)
if (event.getTrigger() instanceof Event) {
Event ev = (Event) event.getTrigger();
if (ev.widget instanceof MenuItem) {
pasteTriggeredByMouse = true;
}
}
// determines the offset of the visible part of the editor from the editor's origin
if (viewer.getControl() instanceof FigureCanvas) {
editorOffsetPoint = ((FigureCanvas) viewer.getControl()).getViewport().getViewLocation();
}
Object content = extractContentfromSystemClipboard();
if (content == null) {
return;
}
Map<String, List<String>> otherNodesCombined = new HashMap<String, List<String>>();
// List of all nodes in the editor
List<String> editorNodeNames = new LinkedList<String>();
for (Object e : viewer.getContents().getChildren()) {
if (e instanceof WorkflowNodePart) {
editorNodeNames.add(((WorkflowNode) ((WorkflowNodePart) e).getModel()).getName());
}
}
if (content instanceof List) {
// create map with node name as key and all other node names as value
for (Object nodePart : (List<?>) content) {
if (nodePart instanceof Connection) {
hasConnections = true;
}
if (nodePart instanceof WorkflowNodePart) {
// create list of all nodes in clipboard
List<String> otherNodes = new ArrayList<String>();
for (Object nodePart2 : (List<?>) content) {
if (nodePart2 instanceof WorkflowNodePart) {
WorkflowNodePart part = (WorkflowNodePart) nodePart2;
WorkflowNode node = (WorkflowNode) part.getModel();
otherNodes.add(node.getName());
}
}
WorkflowNodePart part = (WorkflowNodePart) nodePart;
WorkflowNode node = (WorkflowNode) part.getModel();
// remove current node from other's list
otherNodes.remove(node.getName());
// add nodes from editor to list of other nodes
otherNodes.addAll(editorNodeNames);
otherNodesCombined.put(node.getName(), otherNodes);
}
}
getMinNodeAndLabel(content);
for (Object partToPaste : (List<?>) content) {
if (partToPaste instanceof WorkflowNodePart) {
WorkflowNodePart part = (WorkflowNodePart) partToPaste;
pastedWorkflowNodeParts.add(part);
WorkflowNode node = (WorkflowNode) part.getModel();
String nodeName = node.getName();
String newName = nodeName;
if (otherNodesCombined.get(nodeName).contains(nodeName)) {
newName = "Copy of " + nodeName;
}
int i = 2;
while (otherNodesCombined.get(nodeName).contains(newName)) {
newName = "Copy (" + i++ + ") of " + nodeName;
}
WorkflowNode newNode = new WorkflowNode(copyComponentDescription(node.getComponentDescription()));
newNode.setName(newName);
newNode.setEnabled(node.isEnabled());
workflowNodesToCreate.add(newNode);
addNewNodePosition(nodeConstraintsToCreate, node);
nameMapping.put(node.getIdentifier(), newNode.getIdentifier());
nodeMapping.put(node, newNode);
// mapping to store new nodes determined location without altering the node
// itself
newNodeAndLocationMapping.put(newNode, nodeConstraintsToCreate.get(nodeConstraintPositionCounter));
nodeConstraintPositionCounter++;
Map<String, EndpointDescription> nodesEndpointIDMapping = new HashMap<>();
for (EndpointDescription endpoint : node.getInputDescriptionsManager().getEndpointDescriptions()) {
EndpointDescription newEndpoint = newNode.getInputDescriptionsManager().getEndpointDescription(endpoint.getName());
nodesEndpointIDMapping.put(endpoint.getIdentifier(), newEndpoint);
}
for (EndpointDescription endpoint : node.getOutputDescriptionsManager().getEndpointDescriptions()) {
EndpointDescription newEndpoint = newNode.getOutputDescriptionsManager().getEndpointDescription(endpoint.getName());
nodesEndpointIDMapping.put(endpoint.getIdentifier(), newEndpoint);
}
endpointIDMapping.put(node.getIdentifier(), nodesEndpointIDMapping);
}
if (partToPaste instanceof WorkflowLabel) {
WorkflowLabel label = (WorkflowLabel) partToPaste;
WorkflowLabel newLabel = createCopiedWorkflowLabel(label);
newLabel.setLocation(label.getX() + offset, label.getY() + offset);
workflowLabelsToCreate.add(newLabel);
addNewLabelPosition(labelConstraintsToCreate, label);
labelConstraintPositionCounter++;
}
if (partToPaste instanceof Connection) {
Connection oldConnection = (Connection) partToPaste;
WorkflowNode source = nodeMapping.get(oldConnection.getSourceNode());
WorkflowNode target = nodeMapping.get(oldConnection.getTargetNode());
EndpointDescription sourceOutput =
endpointIDMapping.get(oldConnection.getSourceNode().getIdentifier()).get(oldConnection.getOutput().getIdentifier());
EndpointDescription targetInput =
endpointIDMapping.get(oldConnection.getTargetNode().getIdentifier()).get(oldConnection.getInput().getIdentifier());
List<Location> originalBendpoints = oldConnection.getBendpoints();
int bendpointOffsetX = newNodeAndLocationMapping.get(source).x - oldConnection.getSourceNode().getX();
int bendpointOffsetY = newNodeAndLocationMapping.get(source).y - oldConnection.getSourceNode().getY();
List<Location> bendpointsWithOffset =
ConnectionUtils.translateBendpointListByOffset(originalBendpoints, bendpointOffsetX, bendpointOffsetY);
Connection newConnection = new Connection(source, sourceOutput, target, targetInput, bendpointsWithOffset);
connectionsToCreate.add(newConnection);
}
}
}
WorkflowNodeLabelConnectionCreateCommand nodeAndConnectionCreateCommand = new WorkflowNodeLabelConnectionCreateCommand(
workflowNodesToCreate,
workflowLabelsToCreate,
connectionsToCreate,
(WorkflowDescription) viewer.getContents().getModel(),
nodeConstraintsToCreate, labelConstraintsToCreate);
commandStack.execute(nodeAndConnectionCreateCommand);
selectPastedNodes(workflowNodesToCreate);
}
private void selectPastedNodes(List<WorkflowNode> workflowNodesToCreate) {
// move selection from original components to pasted ones
viewer.deselectAll();
for (Object editPartObject : viewer.getContents().getChildren()) {
if (editPartObject instanceof WorkflowNodePart) {
WorkflowNodePart nodePart = (WorkflowNodePart) editPartObject;
WorkflowNode node = (WorkflowNode) nodePart.getModel();
if (workflowNodesToCreate.contains(node)) {
viewer.appendSelection(nodePart);
}
}
}
}
private Object extractContentfromSystemClipboard() {
String clipboardText = ClipboardHelper.getContentAsStringOrNull();
if (clipboardText != null) {
try {
try (InputStream inputStream = new ByteArrayInputStream(clipboardText.getBytes())) {
return parseJson(
(ObjectNode) JSON_OBJECT_MAPPER.readTree(IOUtils.toString(inputStream, StandardCharsets.UTF_8.name())));
}
} catch (IOException | ParseException | ClassCastException e) {
LogFactory.getLog(getClass())
.debug(StringUtils.format("Pasted content not valid, it will be ignored: '%s' (cause: %s)",
clipboardText, e.toString()));
}
}
return null;
}
private void getMinNodeAndLabel(Object content) {
nodeMinX = Integer.MAX_VALUE;
nodeMinY = Integer.MAX_VALUE;
for (Object partToPast : (List<?>) content) {
if (partToPast instanceof WorkflowNodePart) {
WorkflowNodePart part = (WorkflowNodePart) partToPast;
WorkflowNode node = (WorkflowNode) part.getModel();
getMinXandYForNode(node.getX(), node.getY());
} else if (partToPast instanceof WorkflowLabelPart) {
WorkflowLabelPart part = (WorkflowLabelPart) partToPast;
WorkflowLabel label = (WorkflowLabel) part.getModel();
getMinXandYForLabel(label.getX(), label.getY());
}
}
}
private void getMinXandYForNode(int nodeX, int nodeY) {
if (nodeX < nodeMinX) {
nodeMinX = nodeX;
}
if (nodeY < nodeMinY) {
nodeMinY = nodeY;
}
}
private void getMinXandYForLabel(int labelX, int labelY) {
if (labelX < labelMinX) {
nodeMinX = labelX;
}
if (labelY < labelMinY) {
labelMinY = labelY;
}
}
private void addNewNodePosition(List<Rectangle> nodeConstraintsToCreate, WorkflowNode node) {
WorkflowDescription model = (WorkflowDescription) viewer.getContents().getModel();
if (pasteTriggeredByMouse) {
if (hasConnections) {
nodeConstraintsToCreate.add(nodeConstraintPositionCounter, new Rectangle(
(node.getX() - nodeMinX + editor.getMouseX() + editorOffsetPoint.x),
(node.getY() - nodeMinY + editor.getMouseY() + editorOffsetPoint.y), 0, 0));
} else {
findFreeSpotForNode(editor.getMouseX() + editorOffsetPoint.x, editor.getMouseY() + editorOffsetPoint.y,
node.getComponentDescription().getSize(), nodeConstraintsToCreate);
}
} else {
if (!model.getWorkflowNodes().contains(node) && !hasConnections) {
findFreeSpotForNode(offset, offset, node.getComponentDescription().getSize(), nodeConstraintsToCreate);
} else {
findFreeSpotForNode(node.getX(), node.getY(), node.getComponentDescription().getSize(), nodeConstraintsToCreate);
}
}
}
private void findFreeSpotForNode(int x, int y, ComponentSize componentSize, List<Rectangle> nodeConstraintsToCreate) {
if (!isNodePositionValid(x, y, componentSize)) {
findFreeSpotForNode(x + offset, y + offset, componentSize, nodeConstraintsToCreate);
} else {
for (Rectangle rectangle : nodeConstraintsToCreate) {
if (rectangle.equals(x, y, 0, 0)) {
x += offset;
y += offset;
}
}
nodeConstraintsToCreate.add(nodeConstraintPositionCounter, new Rectangle(x, y, 0, 0));
}
}
private void addNewLabelPosition(List<Rectangle> labelConstraintsToCreate, WorkflowLabel label) {
if (!viewer.getContextMenu().isDirty()) {
findFreeSpotForLabel(editor.getMouseX(), editor.getMouseY(), new Dimension(label.getWidth(), label.getHeight()),
labelConstraintsToCreate);
} else if (viewer.getContextMenu().isDirty()) {
findFreeSpotForLabel(label.getX(), label.getY(), new Dimension(label.getWidth(), label.getHeight()), labelConstraintsToCreate);
}
}
private void findFreeSpotForLabel(int x, int y, Dimension labelSize, List<Rectangle> labelConstraintsToCreate) {
if (!isLabelPositionValid(x, y)) {
findFreeSpotForLabel(x + offset, y + offset, labelSize, labelConstraintsToCreate);
} else {
for (Rectangle rectangle : labelConstraintsToCreate) {
if (rectangle.equals(x, y, labelSize.width, labelSize.height)) {
x += offset * 2;
y += offset * 2;
}
}
if (isLabelPositionValid(x, y)) {
labelConstraintsToCreate.add(labelConstraintPositionCounter,
new Rectangle(x, y, labelSize.width, labelSize.height));
} else {
findFreeSpotForLabel(x + offset, y + offset, labelSize, labelConstraintsToCreate);
}
}
}
private boolean isNodePositionValid(int x, int y, ComponentSize componentSize) {
WorkflowDescription model = (WorkflowDescription) viewer.getContents().getModel();
for (WorkflowNode node : model.getWorkflowNodes()) {
if (node.getComponentDescription().getSize() != null && componentSize != null) {
if (node.getComponentDescription().getSize().equals(ComponentSize.MEDIUM) && !componentSize.equals(ComponentSize.MEDIUM)) {
componentSizeOffset = WorkflowNodePart.WORKFLOW_NODE_WIDTH;
} else {
componentSizeOffset = WorkflowNodePart.SMALL_WORKFLOW_NODE_WIDTH / 2;
}
} else {
// fallback if component description has no size yet or component size is null
componentSizeOffset = WorkflowNodePart.WORKFLOW_NODE_WIDTH;
}
if (x >= node.getX() && x <= node.getX() + componentSizeOffset && y >= node.getY() && y <= node.getY() + componentSizeOffset) {
return false;
}
}
return true;
}
private boolean isLabelPositionValid(int x, int y) {
WorkflowDescription model = (WorkflowDescription) viewer.getContents().getModel();
for (WorkflowLabel label : model.getWorkflowLabels()) {
if (x >= label.getX() && x <= label.getX() + offset && y >= label.getY()
&& y <= label.getY() + offset) {
return false;
}
}
return true;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private List parseJson(ObjectNode rootJsonNode) throws IOException, JsonParseException, ParseException {
List combinedList = new ArrayList();
WorkflowDescriptionPersistenceHandler descriptionHandler = new WorkflowDescriptionPersistenceHandler();
WorkflowEditorEditPartFactory factory = new WorkflowEditorEditPartFactory();
Map<String, WorkflowNode> parsedNodes = null;
if (rootJsonNode.has(WorkflowDescriptionPersistenceHandler.NODES)) {
// parsing content to WorkflowNode
parsedNodes = descriptionHandler.parseNodes((ArrayNode) rootJsonNode.get(WorkflowDescriptionPersistenceHandler.NODES));
for (String key : parsedNodes.keySet()) {
// creating WorkflowNodePart
EditPart createEditPart = factory.createEditPart(null, parsedNodes.get(key));
if (createEditPart instanceof WorkflowNodePart) {
combinedList.add(createEditPart);
}
}
}
List<Connection> parsedConnections = null;
if (rootJsonNode.has(WorkflowDescriptionPersistenceHandler.CONNECTIONS)) {
// parsing content of connections
parsedConnections = descriptionHandler.parseConnections(
(ArrayNode) rootJsonNode.get(WorkflowDescriptionPersistenceHandler.CONNECTIONS), parsedNodes);
combinedList.addAll(parsedConnections);
}
if (rootJsonNode.has(WorkflowDescriptionPersistenceHandler.BENDPOINTS)) {
// parsing content of bendpoints
descriptionHandler.parseBendpoints(
(ArrayNode) rootJsonNode.get(WorkflowDescriptionPersistenceHandler.BENDPOINTS), parsedNodes, parsedConnections);
}
if (rootJsonNode.has(WorkflowDescriptionPersistenceHandler.LABELS)) {
// parsing content of labels
Set<WorkflowLabel> parsedLabel = descriptionHandler.parseLabels(
(ArrayNode) rootJsonNode.get(WorkflowDescriptionPersistenceHandler.LABELS));
combinedList.addAll(parsedLabel);
}
return combinedList;
}
/**
* Copies {@link WorkflowLabel} but doesn't clone it. The identifier needs to remain unique for
* every label.
*
* @param origin {@link WorkflowLabel} to copy.
* @return copied {@link WorkflowLabel}
*/
private WorkflowLabel createCopiedWorkflowLabel(WorkflowLabel origin) {
WorkflowLabel copied = new WorkflowLabel(origin.getText());
copied.setHeaderText(origin.getHeaderText());
copied.setAlpha(origin.getAlphaDisplay());
copied.setColorBackground(origin.getColorBackground());
copied.setColorText(origin.getColorText());
copied.setSize(origin.getWidth(), origin.getHeight());
copied.setLabelPosition(origin.getLabelPosition());
copied.setTextAlignmentType(origin.getTextAlignmentType());
copied.setHeaderAlignmentType(origin.getHeaderAlignmentType());
copied.setHasBorder(origin.hasBorder());
copied.setTextSize(origin.getTextSize());
copied.setHeaderTextSize(origin.getHeaderTextSize());
copied.setColorHeader(origin.getColorHeader());
// copied.setLocation(origin.getX(), origin.getY());
return copied;
}
/**
* Copies {@link ComponentDescription} but doesn't clone it. It is needed that identifiers of
* endpoints are not part of the new {@link ComponentDescription}. It it would, the endpoints of
* the copied {@link ComponentDescription} will be related to existing connections as well.
*
* @param origin {@link ComponentDescription} to copy.
* @return copied {@link ComponentDescription}
*/
private ComponentDescription copyComponentDescription(ComponentDescription origin) {
ComponentDescription copied = new ComponentDescription(origin.getComponentInstallation());
copied.setIsNodeIdTransient(origin.getIsNodeIdTransient());
for (EndpointDescription ep : origin.getInputDescriptionsManager().getDynamicEndpointDescriptions()) {
copied.getInputDescriptionsManager().addDynamicEndpointDescription(ep.getDynamicEndpointIdentifier(),
ep.getName(), ep.getDataType(), ep.getMetaData(), ep.getParentGroupName(), false);
}
for (EndpointDescription ep : origin.getInputDescriptionsManager().getStaticEndpointDescriptions()) {
copied.getInputDescriptionsManager().editStaticEndpointDescription(ep.getName(), ep.getDataType(), ep.getMetaData(),
false);
}
for (EndpointDescription ep : origin.getOutputDescriptionsManager().getDynamicEndpointDescriptions()) {
copied.getOutputDescriptionsManager().addDynamicEndpointDescription(ep.getDynamicEndpointIdentifier(),
ep.getName(), ep.getDataType(), ep.getMetaData(), ep.getParentGroupName(), false);
}
for (EndpointDescription ep : origin.getOutputDescriptionsManager().getStaticEndpointDescriptions()) {
copied.getOutputDescriptionsManager().editStaticEndpointDescription(ep.getName(), ep.getDataType(), ep.getMetaData(),
false);
}
for (EndpointGroupDescription epG : origin.getInputDescriptionsManager().getDynamicEndpointGroupDescriptions()) {
copied.getInputDescriptionsManager().addDynamicEndpointGroupDescription(epG.getDynamicEndpointIdentifier(), epG.getName(),
false);
}
copied.getConfigurationDescription().setConfiguration(new HashMap<>(origin.getConfigurationDescription().getConfiguration()));
return copied;
}
}