/*
* Copyright 2003-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.mps.nodeEditor.datatransfer;
import jetbrains.mps.datatransfer.DataTransferManager;
import jetbrains.mps.datatransfer.PasteEnv;
import jetbrains.mps.datatransfer.PastePlaceHint;
import jetbrains.mps.editor.runtime.impl.DataTransferUtil;
import jetbrains.mps.kernel.model.SModelUtil;
import jetbrains.mps.nodeEditor.SNodeEditorUtil;
import jetbrains.mps.openapi.editor.cells.EditorCell;
import jetbrains.mps.openapi.editor.cells.EditorCell_Collection;
import jetbrains.mps.smodel.SNodeLegacy;
import jetbrains.mps.smodel.SNodeUtil;
import jetbrains.mps.smodel.adapter.MetaAdapterByDeclaration;
import jetbrains.mps.smodel.search.ConceptAndSuperConceptsScope;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.language.SAbstractConcept;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeAccessUtil;
import java.util.Collections;
import java.util.List;
/**
* Author: Sergey Dmitriev.
* Time: Nov 25, 2003 7:27:37 PM
*/
public class NodePaster {
private static final int PASTE_N_A = 0;
private static final int PASTE_TO_TARGET = 1;
private static final int PASTE_TO_PARENT = 2;
private static final int PASTE_TO_ROOT = 3;
private List<SNode> myPasteNodes;
public NodePaster(List<SNode> pasteNodes) {
myPasteNodes = pasteNodes;
}
public boolean canPaste(SNode pasteTarget, PasteEnv pasteEnv) {
return canPaste(pasteTarget, null, pasteEnv) != PASTE_N_A;
}
public boolean canPaste(EditorCell targetCell) {
String role = getRoleFromCell(targetCell);
SNode pasteTarget = targetCell.getSNode();
if (pasteTarget == null) return false;
return canPaste(pasteTarget, role, PasteEnv.NODE_EDITOR) != PASTE_N_A;
}
public void paste(EditorCell targetCell) {
paste(targetCell.getSNode(), getRoleFromCell(targetCell), PasteEnv.NODE_EDITOR, null);
}
public void paste(SNode pasteTarget, PasteEnv pasteEnv, @Nullable String pack) {
paste(pasteTarget, pasteTarget.getRoleInParent(), pasteEnv, pack);
}
public void pasteWithRemove(List<SNode> pasteTargets) {
if (pasteTargets.isEmpty()) {
return;
}
SNode lastNode = pasteTargets.get(pasteTargets.size() - 1);
pasteToParent(lastNode, lastNode.getRoleInParent(), PastePlaceHint.DEFAULT, true);
for (SNode node : pasteTargets) {
if (node.getModel() != null) {
node.delete();
}
}
}
public boolean canPasteWithRemove(List<SNode> pasteTargets) {
if (pasteTargets.isEmpty()) {
return false;
}
SNode firstNode = pasteTargets.get(0);
String role = firstNode.getRoleInParent();
for (SNode node : pasteTargets) {
String role1 = node.getRoleInParent();
if (role1 == null || !role1.equals(role)) {
return false;
}
}
SNode lastNode = pasteTargets.get(pasteTargets.size() - 1);
return canPasteToParent(lastNode, lastNode.getRoleInParent(), true);
}
private void paste(SNode pasteTarget, String role, PasteEnv pasteEnv, @Nullable String pack) {
String role_ = role != null ? role : pasteTarget.getRoleInParent();
int status = canPaste(pasteTarget, role_, pasteEnv);
if (status == PASTE_TO_TARGET) {
pasteToTarget(pasteTarget, null, role_, PastePlaceHint.DEFAULT);
} else if (status == PASTE_TO_PARENT) {
pasteToParent(pasteTarget, role_, PastePlaceHint.DEFAULT, false);
} else if (status == PASTE_TO_ROOT) {
pasteAsRoots(pasteTarget.getModel(), pack);
}
}
public void pasteAsRoots(SModel model, @Nullable String dstPackage) {
for (SNode pasteNode : myPasteNodes) {
model.addRootNode(pasteNode);
if (dstPackage != null) {
SNodeAccessUtil.setProperty(pasteNode, SNodeUtil.property_BaseConcept_virtualPackage, dstPackage);
}
DataTransferManager.getInstance().postProcessNode(pasteNode);
}
}
public boolean canPasteAsRoots() {
for (SNode pasteNode : myPasteNodes) {
if (!pasteNode.getConcept().isRootable()) {
return false;
}
}
return true;
}
public boolean canPasteRelative(SNode anchorNode) {
return canPasteToParent(anchorNode, anchorNode.getRoleInParent(), false);
}
public void pasteRelative(SNode anchorNode, PastePlaceHint placeHint) {
if (anchorNode.getParent() == null) {
pasteAsRoots(anchorNode.getModel(), null);
} else {
pasteToParent(anchorNode, anchorNode.getRoleInParent(), placeHint, false);
}
}
private int canPaste(SNode pasteTarget, String role, PasteEnv pasteEnv) {
if (myPasteNodes == null || myPasteNodes.isEmpty()) {
return PASTE_N_A;
}
String role_ = role != null ? role : pasteTarget.getRoleInParent();
boolean canPasteAsRoot = (pasteTarget.getParent() == null) && canPasteAsRoots(); // root selected and ..
boolean canPasteToTarget = canPasteToTarget(pasteTarget, role_, true);
if (pasteEnv == PasteEnv.PROJECT_TREE) {
// project pane
if (canPasteAsRoot) {
return PASTE_TO_ROOT;
}
if (canPasteToTarget) {
return PASTE_TO_TARGET;
}
} else {
// editor pane
if (canPasteToTarget) {
return PASTE_TO_TARGET;
}
}
if (canPasteToParent(pasteTarget, role_, false)) {
return PASTE_TO_PARENT;
}
return PASTE_N_A;
}
private boolean canPasteToTarget(SNode pasteTarget, String role, boolean allowOneCardinality) {
SNode link = findSuitableLink(new SNodeLegacy(pasteTarget).getConceptDeclarationNode(), role);
if (link != null && SModelUtil.isAggregation(link)) {
if (!allowOneCardinality) {
return SModelUtil.isMultipleLinkDeclaration(link);
} else {
return true;
}
}
return false;
}
private void pasteToTarget(final SNode pasteTarget, final SNode anchorNode, String role, final PastePlaceHint placeHint) {
final SNode link = findSuitableLink(new SNodeLegacy(pasteTarget).getConceptDeclarationNode(), role);
// unique child?
if (!SModelUtil.isMultipleLinkDeclaration(link)) {
assert myPasteNodes.size() == 1 : "cannot paste multiple children for role '" + SModelUtil.getLinkDeclarationRole(link) + "'";
SNode node = normalizeForLink(myPasteNodes.get(0), MetaAdapterByDeclaration.getContainmentLink(link));
SNodeEditorUtil.setSingleChild(pasteTarget, SModelUtil.getLinkDeclarationRole(link), node);
DataTransferManager.getInstance().postProcessNode(node);
return;
}
SNode currentAnchorNode = anchorNode;
boolean insertBefore = placeHint == PastePlaceHint.BEFORE_ANCHOR;
for (SNode pasteNode : myPasteNodes) {
SNode nodeToPaste = normalizeForLink(pasteNode, MetaAdapterByDeclaration.getContainmentLink(link));
String r = SModelUtil.getGenuineLinkRole(link);
SNode realAnchor = insertBefore ? currentAnchorNode : currentAnchorNode == null ? pasteTarget.getFirstChild() : currentAnchorNode.getNextSibling();
pasteTarget.insertChildBefore(r, nodeToPaste, realAnchor);
DataTransferManager.getInstance().postProcessNode(nodeToPaste);
currentAnchorNode = nodeToPaste;
insertBefore = false;
}
// delete original anchor if it was abstract concept
if (anchorNode != null && DataTransferUtil.isAbstract(new SNodeLegacy(anchorNode).getConceptDeclarationNode())) {
anchorNode.delete();
}
}
private SNode normalizeForLink(SNode pasteNode, SContainmentLink link) {
SAbstractConcept targetConcept = link.getTargetConcept();
if (pasteNode.isInstanceOfConcept(targetConcept)) {
return pasteNode;
} else if (DataTransferManager.getInstance().canWrapInto(pasteNode, targetConcept)) {
return DataTransferManager.getInstance().wrapInto(pasteNode, targetConcept);
} else {
throw new RuntimeException("node " + pasteNode + "can't be normalized for link " + link);
}
}
private boolean canPasteToParent(SNode anchorNode, String role, boolean exactly) {
NodeAndRole nodeAndRole = getActualAnchorNode(anchorNode, role, exactly);
return (nodeAndRole != null && nodeAndRole.myNode != null);
}
private void pasteToParent(SNode pasteTarget, String role, PastePlaceHint placeHint, boolean exactly) {
SNode actualPasteTarget;
NodeAndRole nodeAndRole = getActualAnchorNode(pasteTarget, role, exactly);
SNode actualAnchorNode = nodeAndRole.myNode;
String actualRole = nodeAndRole.myRole;
actualPasteTarget = actualAnchorNode.getParent();
if (actualPasteTarget == null) {
return;
}
pasteToTarget(actualPasteTarget, actualAnchorNode, actualRole, placeHint);
}
public NodeAndRole getActualAnchorNode(SNode firstAnchorNode, String firstRole, boolean exactly) {
String role = firstRole;
SNode anchorNode = firstAnchorNode;
while (anchorNode != null) {
SNode container = anchorNode.getParent();
if (container == null) {
return null;
}
if (canPasteToTarget(container, role, firstAnchorNode == anchorNode)) {
return new NodeAndRole(anchorNode, role);
}
if (exactly) {
break;
}
anchorNode = container;
role = anchorNode.getRoleInParent();
}
return null;
}
private SNode findSuitableLink(SNode sourceConcept, String role) {
//todo[MM] !!! remove "role" here!!!
List<SNode> links;
if (role != null) {
SNode link = new ConceptAndSuperConceptsScope(sourceConcept).getMostSpecificLinkDeclarationByRole(role);
if (link != null) {
links = Collections.singletonList(link);
} else {
links = Collections.emptyList();
}
} else {
links = new ConceptAndSuperConceptsScope(sourceConcept).getLinkDeclarationsExcludingOverridden();
}
for (SNode link : links) {
SAbstractConcept linkTargetConcept = MetaAdapterByDeclaration.getConcept(SModelUtil.getLinkDeclarationTarget(link));
boolean suitable = true;
for (SNode pasteNode : myPasteNodes) {
if (!pasteNode.isInstanceOfConcept(linkTargetConcept) && !DataTransferManager.getInstance().canWrapInto(pasteNode, linkTargetConcept)) {
suitable = false;
break;
}
}
if (suitable) {
if (myPasteNodes.size() == 1 || SModelUtil.isMultipleLinkDeclaration(link)) {
return link;
}
}
}
return null;
}
private String getRoleFromCell(EditorCell targetCell) {
String role = targetCell.getRole();
if (role != null) return role;
EditorCell_Collection actualCollection = (targetCell instanceof EditorCell_Collection) ? (EditorCell_Collection) targetCell : targetCell.getParent();
if (actualCollection != null) role = ((jetbrains.mps.nodeEditor.cells.EditorCell_Collection) actualCollection).getCellNodesRole();
while (actualCollection != null && role == null) {
actualCollection = actualCollection.getParent();
if (actualCollection == null) break;
role = ((jetbrains.mps.nodeEditor.cells.EditorCell_Collection) actualCollection).getCellNodesRole();
}
if (role == null) {
SNode pasteTarget = targetCell.getSNode();
role = pasteTarget.getRoleInParent();
}
return role;
}
public static class NodeAndRole {
public String myRole;
public SNode myNode;
public NodeAndRole(SNode node, String role) {
this.myRole = role;
this.myNode = node;
}
}
}