/* * Copyright 2003-2014 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 org.jetbrains.mps.openapi.model; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.language.SAbstractConcept; import org.jetbrains.mps.openapi.language.SConcept; import org.jetbrains.mps.openapi.language.SContainmentLink; import org.jetbrains.mps.openapi.module.SRepository; import org.jetbrains.mps.util.Condition; import org.jetbrains.mps.util.DescendantsTreeIterator; import org.jetbrains.mps.util.FilterIterator; import java.util.Collections; import java.util.Iterator; /** * This class implement complex operations on the node structure. * Unlike the SNode implementations, which may focus on their specific needs, this class should consider all cases, * e.g. replacing a node with a root node from another model */ public class SNodeUtil { /** * Returns whether the given node belongs to the repository (or to one of its parent repositories). */ public static boolean isAccessible(@NotNull SNode node, @NotNull SRepository inRepository) { SModel model = node.getModel(); if (model == null) return false; SRepository mrep = model.getRepository(); if (mrep == null) return false; mrep.getModelAccess().checkReadAccess(); if (inRepository == mrep) { return true; } // FIXME this is a hack to deal with incomplete story of repository relationship. // We have at least two repositories, ProjectRepository and MPSModuleRepository, and with repository.getParent() dropped, there are no chance // to figure out node coming from MPSModuleRepository is visible in ProjectRepository (EditorComponent.editNode() passes here project's repository // but node might come from an MPSModuleRepository (e.g. temp node from console), and they don't match. // Here, assume node is accessible even if repositories are different if its module is identical in both. inRepository.getModelAccess().checkReadAccess(); return model.getModule() == inRepository.getModule(model.getModule().getModuleId()); } //todo move to snode class public static boolean isInstanceOf(@Nullable SNode node, @NotNull SAbstractConcept concept) { if (node == null) return false; SConcept c = node.getConcept(); return c.isSubConceptOf(concept); } /** * Replaces a node with another, preserving attributes */ public static SNode replaceWithAnother(@NotNull SNode node, SNode replacer) { SNode nodeParent = node.getParent(); if (nodeParent == null) { SModel model = node.getModel(); if (model != null) { node.delete(); model.addRootNode(replacer); } return replacer; } SContainmentLink role = node.getContainmentLink(); assert role != null; if (replacer != null) { // old and new child can have the same node Id // thus it is important to remove old child first SNode anchor = node.getNextSibling(); nodeParent.removeChild(node); SNode replacerParent = replacer.getParent(); if (replacerParent != null) { replacerParent.removeChild(replacer); } nodeParent.insertChildBefore(role, replacer, anchor); } else { nodeParent.removeChild(node); } return replacer; } /** * Iterates over all nodes in the given model. */ public static Iterable<SNode> getDescendants(@NotNull SModel model) { return new NodesIterable(model); } /** * Iterates over the subtree starting at the given node. */ @NotNull public static Iterable<SNode> getDescendants(@NotNull SNode node) { return new DescendantsIterable(node, null, true); } /** * Iterates over the subtree starting at the given node. * * @param condition if not null, acts like a filter * @param includeFirst false to skip the root node from the returned sequence */ @NotNull public static Iterable<SNode> getDescendants(@NotNull SNode node, @Nullable Condition<SNode> condition, boolean includeFirst) { return new DescendantsIterable(node, condition, includeFirst); } /** * Iterate over subtrees of each node in a given sequence of 'root' nodes */ @NotNull public static Iterable<SNode> getDescendants(@NotNull Iterable<SNode> roots) { return new ConcatNodesIterable(roots); } private static class DescendantsIterable implements Iterable<SNode> { private final SNode myNode; private final Condition<SNode> myCondition; private final boolean myIncludeFirst; private DescendantsIterable(@NotNull SNode node, @Nullable final Condition<SNode> condition, boolean includeFirst) { myNode = node; myCondition = condition; myIncludeFirst = includeFirst; } @Override public Iterator<SNode> iterator() { Iterator<SNode> it = new DescendantsTreeIterator(myNode); if (!myIncludeFirst && it.hasNext()) { it.next(); } if (myCondition != null) { it = new FilterIterator<SNode>(it, myCondition); } return it; } } private static class NodesIterable implements Iterable<SNode> { private SModel mySModel; public NodesIterable(SModel sModel) { mySModel = sModel; } @Override public Iterator<SNode> iterator() { return new NodesIterator(mySModel.getRootNodes().iterator()); } } private static class ConcatNodesIterable implements Iterable<SNode> { private Iterable<SNode> myRoots; public ConcatNodesIterable(Iterable<SNode> roots) { myRoots = roots; } @Override public Iterator<SNode> iterator() { return new NodesIterator(myRoots.iterator()); } } private static class NodesIterator implements Iterator<SNode> { private Iterator<SNode> myRoots; private Iterator<SNode> myCurrent; public NodesIterator(Iterator<SNode> roots) { myRoots = roots; myCurrent = getIterForNextRoot(roots); } @Override public boolean hasNext() { moveToNextRootIfNeeded(); return myCurrent.hasNext(); } @Override public SNode next() { moveToNextRootIfNeeded(); return myCurrent.next(); } @Override public void remove() { throw new UnsupportedOperationException(); } private void moveToNextRootIfNeeded() { if (myCurrent.hasNext()) return; if (!myRoots.hasNext()) return; while (myRoots.hasNext() && !(myCurrent.hasNext())) { myCurrent = getIterForNextRoot(myRoots); } } private Iterator<SNode> getIterForNextRoot(Iterator<SNode> roots) { if (!roots.hasNext()) return Collections.<SNode>emptyList().iterator(); SNode next = roots.next(); return SNodeUtil.getDescendants(next).iterator(); } } }