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); } }