package jetbrains.mps.editor.runtime;
/*Generated by MPS */
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import org.jetbrains.mps.openapi.model.SNode;
import jetbrains.mps.openapi.editor.EditorContext;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import jetbrains.mps.logging.Logger;
import org.apache.log4j.LogManager;
import jetbrains.mps.internal.collections.runtime.CollectionSequence;
import java.util.ArrayList;
import jetbrains.mps.util.ComputeRunnable;
import jetbrains.mps.util.ModelComputeRunnable;
import jetbrains.mps.util.Computable;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import jetbrains.mps.internal.collections.runtime.Sequence;
import jetbrains.mps.editor.runtime.commands.EditorCommandAdapter;
import jetbrains.mps.openapi.editor.selection.SelectionManager;
import jetbrains.mps.smodel.ModelAccessHelper;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;
import jetbrains.mps.smodel.behaviour.BHReflection;
import jetbrains.mps.core.aspects.behaviour.SMethodTrimmedId;
import jetbrains.mps.openapi.editor.cells.EditorCell;
import jetbrains.mps.openapi.editor.cells.EditorCell_Collection;
import java.util.Iterator;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.AttributeOperations;
import jetbrains.mps.editor.runtime.impl.CellUtil;
import jetbrains.mps.openapi.editor.cells.traversal.CellTreeIterable;
import jetbrains.mps.openapi.editor.cells.CellTraversalUtil;
import org.jetbrains.annotations.Nullable;
public class IntelligentNodeMover {
@NotNull
private final Collection<SNode> myNodesToMove;
@NotNull
private final EditorContext myEditorContext;
private final boolean myIsForward;
private boolean myIsValid;
private SContainmentLink myCommonNodesContainmentLink;
private SNode myCommonNodesParent;
private static final Logger LOG = Logger.wrap(LogManager.getLogger(IntelligentNodeMover.class));
public IntelligentNodeMover(@NotNull SNode node, @NotNull EditorContext editorContext, boolean forward) {
this(CollectionSequence.fromCollectionAndArray(new ArrayList<SNode>(), node), editorContext, forward);
}
public IntelligentNodeMover(@NotNull Collection<SNode> nodes, @NotNull EditorContext editorContext, boolean forward) {
myNodesToMove = nodes;
myEditorContext = editorContext;
myIsForward = forward;
}
/**
* move nodes
* Throws exception if mover has invalid state. State of mover is valid iff all of the following conditions are met
* 1) Collection of nodes to move is not empty
* 2) All nodes to move have same non-null parent
* 3) All nodes to move have same non-null containment link
* Check for isValid() before running this method
*
* @throws IllegalStateException if mover has invalid state
* @return true if nodes were moved. Otherwise if there is no place for nodes to be moved false is returned
*/
public boolean move() {
myIsValid = isValid();
if (!(myIsValid)) {
throw new IllegalStateException("IntelligentNodeMover has invalid state. Nodes to move have different parents of different containment links");
}
ComputeRunnable<Boolean> mover = new ModelComputeRunnable<Boolean>(new Computable<Boolean>() {
public Boolean compute() {
IntelligentNodeMover.PlaceToMove placeToMove = findPlaceToMove();
if (placeToMove == null) {
return false;
}
Iterable<SNode> intersection = ListSequence.fromList(SNodeOperations.getNodeAncestors(placeToMove.myParent, null, false)).intersect(CollectionSequence.fromCollection(myNodesToMove));
if (Sequence.fromIterable(intersection).isNotEmpty()) {
SNode first = Sequence.fromIterable(intersection).first();
LOG.error("Possible creation of cyclic tree. Node [\"" + first + "\"; concept: " + SNodeOperations.getConcept(first) + "; id: " + first.getNodeId() + "] is supposed to be moved inside itself. Moving was cancelled");
return false;
}
doMove(placeToMove);
return true;
}
});
myEditorContext.getRepository().getModelAccess().executeCommand(new EditorCommandAdapter(mover, myEditorContext));
boolean result = mover.getResult();
if (result) {
myEditorContext.flushEvents();
myEditorContext.getRepository().getModelAccess().runReadAction(new Runnable() {
public void run() {
if (CollectionSequence.fromCollection(myNodesToMove).count() == 1) {
myEditorContext.select(getBoundaryNode());
} else {
SelectionManager selectionManager = myEditorContext.getSelectionManager();
selectionManager.setSelection(selectionManager.createRangeSelection(CollectionSequence.fromCollection(myNodesToMove).first(), CollectionSequence.fromCollection(myNodesToMove).last()));
}
}
});
}
return result;
}
/**
* Checks validity
* Returns true if all the following conditions are met
* 1) Collection of nodes to move is not empty
* 2) All nodes to move have same non-null parent
* 3) All nodes to move have same non-null containment link
*
* @return true if valid
*/
public boolean isValid() {
return new ModelAccessHelper(myEditorContext.getRepository()).runReadAction(new Computable<Boolean>() {
public Boolean compute() {
if (CollectionSequence.fromCollection(myNodesToMove).isEmpty()) {
return false;
}
SNode commonParent = null;
SContainmentLink commonLink = null;
for (SNode node : CollectionSequence.fromCollection(myNodesToMove)) {
if (node == null) {
return false;
}
SContainmentLink link = getNodesContainmentLink(node);
if (link == null) {
return false;
}
if (commonLink == null) {
commonLink = link;
} else if (commonLink != link) {
return false;
}
SNode parent = node.getParent();
assert parent != null;
if (commonParent == null) {
commonParent = parent;
} else if (commonParent != parent) {
return false;
}
}
return true;
}
});
}
@NotNull
private SContainmentLink getNodesCommonContainmentLink() {
assert myIsValid;
if (myCommonNodesContainmentLink == null) {
assert CollectionSequence.fromCollection(myNodesToMove).isNotEmpty();
SNode first = CollectionSequence.fromCollection(myNodesToMove).first();
assert first != null;
myCommonNodesContainmentLink = getNodesContainmentLink(first);
assert myCommonNodesContainmentLink != null;
}
return myCommonNodesContainmentLink;
}
@NotNull
private SNode getNodesCommonParent() {
assert myIsValid;
if (myCommonNodesParent == null) {
assert CollectionSequence.fromCollection(myNodesToMove).isNotEmpty();
SNode first = CollectionSequence.fromCollection(myNodesToMove).first();
assert first != null;
myCommonNodesParent = SNodeOperations.getParent(first);
assert myCommonNodesParent != null;
}
return myCommonNodesParent;
}
private void doMove(IntelligentNodeMover.PlaceToMove place) {
SNode nextAnchor = place.myAnchor;
for (SNode node : myNodesToMove) {
getNodesCommonParent().removeChild(node);
SContainmentLink link = (SNodeOperations.isInstanceOf(node, MetaAdapterFactory.getConcept(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x9d98713f247885aL, "jetbrains.mps.lang.core.structure.ChildAttribute")) ? MetaAdapterFactory.getContainmentLink(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x10802efe25aL, 0x47bf8397520e5942L, "smodelAttribute") : place.myLink);
if (SNodeOperations.isInstanceOf(node, MetaAdapterFactory.getConcept(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x9d98713f247885aL, "jetbrains.mps.lang.core.structure.ChildAttribute"))) {
BHReflection.invoke(SNodeOperations.cast(node, MetaAdapterFactory.getConcept(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x9d98713f247885aL, "jetbrains.mps.lang.core.structure.ChildAttribute")), SMethodTrimmedId.create("setLink", MetaAdapterFactory.getConcept(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x9d98713f247885aL, "jetbrains.mps.lang.core.structure.ChildAttribute"), "BpxLfMirzM"), place.myLink);
}
if (place.myIsAfter) {
place.myParent.insertChildAfter(link, node, nextAnchor);
nextAnchor = node;
} else {
place.myParent.insertChildBefore(link, node, nextAnchor);
}
}
}
@NotNull
private SNode getBoundaryNode() {
assert myIsValid;
return (myIsForward ? CollectionSequence.fromCollection(myNodesToMove).last() : CollectionSequence.fromCollection(myNodesToMove).first());
}
private IntelligentNodeMover.PlaceToMove findPlaceToMove() {
SNode sibling = getSibling();
if (sibling != null) {
EditorCell siblingCell = myEditorContext.getEditorComponent().findNodeCell(sibling);
IntelligentNodeMover.PlaceToMove placeToMoveInsideSibling = findPlaceToMoveInsideCell(siblingCell);
return (placeToMoveInsideSibling != null ? placeToMoveInsideSibling : new IntelligentNodeMover.PlaceToMove(getNodesCommonParent(), getNodesCommonContainmentLink(), sibling, myIsForward));
} else {
EditorCell anchorCell = myEditorContext.getEditorComponent().findNodeCell(getBoundaryNode());
EditorCell_Collection parentCell = anchorCell.getParent();
while (parentCell != null) {
Iterator<EditorCell> cellIterator = parentCell.iterator(anchorCell, myIsForward);
while (cellIterator.hasNext()) {
IntelligentNodeMover.PlaceToMove place = findPlaceToMoveInsideCell(cellIterator.next());
if (place != null) {
return place;
}
}
if (parentCell.isBig()) {
SNode anchor = parentCell.getSNode();
SNode parent = SNodeOperations.getParent(anchor);
if (parent != null) {
SContainmentLink anchorLink = getNodesContainmentLink(anchor);
assert anchorLink != null;
if (anchorLink.isMultiple() && isSimilarLink(anchorLink)) {
return new IntelligentNodeMover.PlaceToMove(parent, anchorLink, anchor, myIsForward);
}
}
}
anchorCell = parentCell;
parentCell = parentCell.getParent();
}
return null;
}
}
private SNode getSibling() {
SNode boundaryNode = getBoundaryNode();
Iterable<SNode> childrenAndChildAttributes = AttributeOperations.getChildNodesAndAttributes(getNodesCommonParent(), getNodesCommonContainmentLink());
Iterator<SNode> iterator = Sequence.fromIterable(childrenAndChildAttributes).iterator();
SNode prev = null;
while (iterator.hasNext()) {
SNode next = iterator.next();
if (myIsForward) {
if (prev == boundaryNode) {
return next;
}
} else {
if (next == boundaryNode) {
return prev;
}
}
prev = next;
}
return null;
}
private IntelligentNodeMover.PlaceToMove findPlaceToMoveInsideCell(EditorCell cell) {
if (cell == null) {
return null;
}
EditorCell cellToMove = findCellToMoveInsideCell(cell);
if (cellToMove != null) {
SContainmentLink cellContainmentLink = CellUtil.getCellContainmentLink(cellToMove);
assert cellContainmentLink != null;
return new IntelligentNodeMover.PlaceToMove(cellToMove.getSNode(), cellContainmentLink, null, myIsForward);
}
return null;
}
private EditorCell findCellToMoveInsideCell(@NotNull EditorCell parentCell) {
CellTreeIterable cellIterable = CellTraversalUtil.iterateTree(parentCell, parentCell, myIsForward);
for (EditorCell cell : cellIterable) {
if (isProperCellToMove(cell)) {
return cell;
}
}
return null;
}
private boolean isProperCellToMove(@NotNull EditorCell cell) {
SContainmentLink link = CellUtil.getCellContainmentLink(cell);
return link != null && link.isMultiple() && isSimilarLink(link);
}
private boolean isSimilarLink(@NotNull SContainmentLink link) {
return eq_9l6nqc_a0a0a53_0(link.getName(), getNodesCommonContainmentLink().getName()) && eq_9l6nqc_a0a0a53(link.getTargetConcept(), getNodesCommonContainmentLink().getTargetConcept());
}
private static class PlaceToMove {
@NotNull
public final SNode myParent;
@NotNull
public final SContainmentLink myLink;
@Nullable
public final SNode myAnchor;
public final boolean myIsAfter;
public PlaceToMove(@NotNull SNode parent, @NotNull SContainmentLink link, @Nullable SNode anchor, boolean after) {
myParent = parent;
myLink = link;
myAnchor = anchor;
this.myIsAfter = after;
}
}
/**
*
* @param node node to start finding from
* @param editorContext current editor context
* @return ancestor of the node which is contained in multiple role
*/
public static SNode findNodeToMove(@NotNull final SNode node, @NotNull EditorContext editorContext) {
ModelComputeRunnable<SNode> findNode = new ModelComputeRunnable<SNode>(new Computable<SNode>() {
public SNode compute() {
SContainmentLink containmentLink = getNodesContainmentLink(node);
SNode current = node;
while (containmentLink != null) {
if (containmentLink.isMultiple()) {
return current;
}
current = SNodeOperations.getParent(current);
assert current != null;
containmentLink = getNodesContainmentLink(current);
}
return null;
}
});
return findNode.runRead(editorContext.getRepository().getModelAccess());
}
private static SContainmentLink getNodesContainmentLink(@NotNull SNode node) {
if (SNodeOperations.isInstanceOf(node, MetaAdapterFactory.getConcept(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x9d98713f247885aL, "jetbrains.mps.lang.core.structure.ChildAttribute"))) {
return ((SContainmentLink) BHReflection.invoke(SNodeOperations.cast(node, MetaAdapterFactory.getConcept(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x9d98713f247885aL, "jetbrains.mps.lang.core.structure.ChildAttribute")), SMethodTrimmedId.create("getLink", MetaAdapterFactory.getConcept(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x9d98713f247885aL, "jetbrains.mps.lang.core.structure.ChildAttribute"), "BpxLfMirzf")));
}
return node.getContainmentLink();
}
private static boolean eq_9l6nqc_a0a0a53(Object a, Object b) {
return (a != null ? a.equals(b) : a == b);
}
private static boolean eq_9l6nqc_a0a0a53_0(Object a, Object b) {
return (a != null ? a.equals(b) : a == b);
}
}