/* * 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; import jetbrains.mps.kernel.model.SModelUtil; import jetbrains.mps.openapi.editor.cells.CellTraversalUtil; import jetbrains.mps.openapi.editor.cells.EditorCell; import jetbrains.mps.smodel.ModelAccess; import jetbrains.mps.smodel.SNodeLegacy; import jetbrains.mps.smodel.SNodeUtil; import jetbrains.mps.util.Computable; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.openapi.model.SNode; import java.util.HashSet; import java.util.Set; public class ChildrenCollectionFinder { @NotNull private final EditorCell myCurrent; @NotNull private final EditorCell myAnchor; private final boolean myForward; private final boolean myCheckFirst; public ChildrenCollectionFinder(@NotNull EditorCell current, @NotNull EditorCell anchor, boolean forward, boolean checkFirst) { myCurrent = current; myAnchor = anchor; myForward = forward; myCheckFirst = checkFirst; } public ChildrenCollectionFinder(@NotNull EditorCell current, boolean forward, boolean checkFirst) { this(current, current, forward, checkFirst); } public EditorCell find() { return ModelAccess.instance().runReadAction(new Computable<EditorCell>() { @Override public EditorCell compute() { if (myCheckFirst && isMultipleCollectionCell(myCurrent)) { return myCurrent; } SNode anchorNode = getLCA(myAnchor.getSNode(), myCurrent.getSNode()); if (anchorNode == null) { return null; } // Note: traverser doesn't visit parent nodes of the current node, // if our anchor is from another subtree, search parents (up to LCA) first // TODO remove if (anchorNode != myCurrent.getSNode()) { EditorCell curr = myCurrent; while (curr != null && curr.getSNode() != anchorNode) { if (isMultipleCollectionCell(curr)) { return curr; } curr = curr.getParent(); } if (curr != null && curr.getSNode() == anchorNode && isMultipleCollectionCell(curr)) { return curr; } } for (EditorCell current : CellTraversalUtil.iterateTree(null, myCurrent, myForward).skipStart()) { SNode currentNode = current.getSNode(); if (!jetbrains.mps.util.SNodeOperations.isAncestor(anchorNode, currentNode)) { return null; } if (isMultipleCollectionCell(current)) { return current; } } return null; } }); } private static boolean isMultipleCollectionCell(EditorCell current) { if (current.getRole() != null) { String role = current.getRole(); SNode currentNode = current.getSNode(); SNode linkDeclaration = new SNodeLegacy(currentNode).getLinkDeclaration(role); if (linkDeclaration != null && !SNodeUtil.getLinkDeclaration_IsReference(linkDeclaration) && SModelUtil.isMultipleLinkDeclaration(linkDeclaration)) { return true; } } return false; } private static SNode getLCA(SNode left, SNode right) { if (left == right || right == null || left == null) { return left == null ? right : left; } Set<SNode> parents = new HashSet<SNode>(); while (left != null) { parents.add(left); left = left.getParent(); } while (right != null) { if (parents.contains(right)) return right; right = right.getParent(); } return null; } }