core/kernel/source/jetbrains/mps/smodel/CopyUtil.java (146 lines of code) (raw):

/* * Copyright 2003-2024 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.smodel; import jetbrains.mps.RuntimeFlags; import jetbrains.mps.lang.smodel.generator.smodelAdapter.AttributeOperations; import jetbrains.mps.util.SNodeOperations; import org.jetbrains.mps.openapi.language.SContainmentLink; import org.jetbrains.mps.openapi.language.SProperty; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SNodeUtil; import org.jetbrains.mps.openapi.model.SReference; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public final class CopyUtil { private CopyUtil() { } public static void copyModelContent(SModel from, SModel to) { for (SNode root : from.getRootNodes()) { to.addRootNode(copy(root)); } } /** * @deprecated use appropriately configure {@link NodeDuplicator}, instead */ @Deprecated(since = "2024.1", forRemoval = true) public static void copyModelContentAndUpdateCrossRootReferences(SModel from, SModel to) { // copy content and update references for targets in the same model to point to copied counterparts new NodeDuplicator().keepNodeId(false).duplicate(from, to); } public static void copyModelContentAndPreserveIds(SModel from, SModel to) { for (SNode root : from.getRootNodes()) { to.addRootNode(copyAndPreserveId(root, true)); } } private static void copyModelContentAndPreserveIds(jetbrains.mps.smodel.SModel from, jetbrains.mps.smodel.SModel to) { for (SNode root : from.getRootNodes()) { to.addRootNode(copyAndPreserveId(root, true)); } } public static void copyModelProperties(jetbrains.mps.smodel.SModel from, jetbrains.mps.smodel.SModel to) { from.copyPropertiesTo(to); } public static jetbrains.mps.smodel.SModel copyModel(jetbrains.mps.smodel.SModel model) { jetbrains.mps.smodel.SModel copy = model.createEmptyCopy(); copyModelContentAndPreserveIds(model, copy); copyModelProperties(model, copy); return copy; } public static List<SNode> copy(List<SNode> nodes) { return copy(nodes, new HashMap<>()); } public static List<SNode> copy(List<SNode> nodes, Map<SNode, SNode> mapping) { List<SNode> result = clone(nodes, mapping); for (SNode node : nodes) { addReferences(node, mapping, false); } return result; } public static SNode copy(SNode node) { return copy(node, new HashMap<>(), true); } public static SNode copyAndPreserveId(SNode node) { return copyAndPreserveId(node, true); } public static SNode copyAndPreserveId(SNode node, boolean cloneRefs) { HashMap<SNode, SNode> mapping = new HashMap<>(); SNode result = clone(node, mapping, true); for (SNode sourceNode : mapping.keySet()) { ((jetbrains.mps.smodel.SNode) mapping.get(sourceNode)).setId(sourceNode.getNodeId()); } addReferences(node, mapping, cloneRefs); return result; } public static SNode copy(SNode node, boolean copyAttributes) { return copy(node, new HashMap<>(), copyAttributes); } public static SNode copy(SNode node, Map<SNode, SNode> mapping, boolean copyAttributes) { SNode result = clone(node, mapping, copyAttributes); addReferences(node, mapping, false); return result; } public static List<SNode> copyAndPreserveId(List<SNode> nodes, Map<SNode, SNode> mapping) { List<SNode> result = clone(nodes, mapping); for (SNode sourceNode : mapping.keySet()) { ((jetbrains.mps.smodel.SNode) mapping.get(sourceNode)).setId(sourceNode.getNodeId()); } for (SNode node : nodes) { addReferences(node, mapping, false); } return result; } private static SNode clone(SNode node, Map<SNode, SNode> mapping, boolean copyAttributes) { if (node == null) return null; jetbrains.mps.smodel.SNode result = new jetbrains.mps.smodel.SNode(node.getConcept()); mapping.put(node, result); copyProperties(node, result); copyUserObjects(node, result); NodeIdentityComponent.getInstance().configure(result, null, node); for (SNode child : node.getChildren()) { if (!copyAttributes && AttributeOperations.isAttribute(child)) continue; SContainmentLink role = child.getContainmentLink(); assert role != null; result.addChild(role, clone(child, mapping, copyAttributes)); } return result; } private static List<SNode> clone(List<? extends SNode> nodes, Map<SNode, SNode> mapping) { List<SNode> results = new ArrayList<>(); for (SNode node : nodes) { results.add(clone(node, mapping, true)); } return results; } public static void copyProperties(SNode from, SNode to) { for (SProperty p : from.getProperties()) { to.setProperty(p, from.getProperty(p)); } } public static void copyUserObjects(SNode from, final SNode to) { for (Object key : from.getUserObjectKeys()) { if (key instanceof InherentUserObject) { continue; } to.putUserObject(key, from.getUserObject(key)); } } public static void addReferences(SNode root, Map<SNode, SNode> mapping, boolean forceCloneRefs) { if (root == null) { return; } Iterable<SNode> thisAndDesc = SNodeUtil.getDescendants(root); final boolean cloneRefs = forceCloneRefs || RuntimeFlags.isMergeDriverMode(); for (SNode inputNode : thisAndDesc) { SNode outputNode = mapping.get(inputNode); if (outputNode == null) { continue; } for (SReference ref : inputNode.getReferences()) { // FIXME stacktrace in MPS-29786 reveals we may copy a detached node with a dynamic reference - // there's no reason even to try to resolve such a reference, even 'silently'. OTOH, not sure // if it's reasonable to copy it bluntly with describeTarget(), what if its target is among // copied ancestors/descendants mapping (for a regular reference)? No idea how to tackle this properly, // without cast to DynamicReference and creepy ifs // XXX perhaps, shall attempt reference resolve only when inputNode.getModel() != null // In fact, StaticReference.getTarget() needs source model (unlike DR, it doesn't fail with AE if there's none). // OTOH, SR may have immature SNode (quite likely for a detached node) and skip reference resolve altogether // in that case would be great to keep logic that maps target from original to copied node. SNode inputTargetNode = cloneRefs ? null : jetbrains.mps.util.SNodeOperations.getTargetNodeSilently(ref); if (inputTargetNode == null) { //broken reference or need to clone outputNode.setReference(ref.getLink(), ref.describeTarget()); // XXX here used to be code that didn't copy dynamic references unless cloneRefs, no idea why; removed. } else { outputNode.setReferenceTarget(ref.getLink(), mapping.getOrDefault(inputTargetNode, inputTargetNode)); } } } } }