/* * 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 jetbrains.mps.textgen.trace; import jetbrains.mps.smodel.CopyUtil; import jetbrains.mps.smodel.SNodePointer; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SNodeReference; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.module.SRepository; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map.Entry; /** * Mechanism Generator uses to keep trace of origin for transformed nodes. As the package name suggests, it's rather part of [debuginfo-api] module, * however, generated code uses this class in runtime, and as long as [debuginfo-api] is auxiliary (not part of the [kernel]/mps-core), it's better to keep this class * here. After all, [debuginfo-api] is not the only client of the trace. * * Perhaps, shall push this class even further, down to [smodel], as it's quite generic. Meanwhile, there's CopyUtil that got quite some dependencies to [kernel]. * Once either CopyUtil's dependencies are in [smodel], or there's simplified alternative (I don't need too much from CopyUtil here, in fact), consider moving * the class further. */ public final class TracingUtil { // key for 'node user object' used to keep track of 'original input node' for a generated node // only used when output node has been created as a 'copy' of 'original input node' public static final String ORIGINAL_INPUT_NODE = "originalInputNode"; public static SNode copyWithTrace(SNode node) { if (node == null) { return null; } return copyWithTrace(Collections.singletonList(node)).get(0); } public static List<SNode> copyWithTrace(List<SNode> nodes) { HashMap<SNode, SNode> nodeMap = new HashMap<SNode, SNode>(); List<SNode> result = CopyUtil.copy(nodes, nodeMap); for (Entry<SNode, SNode> entry : nodeMap.entrySet()) { SNodeReference input = getInput(entry.getKey()); if (input != null) { putInput(entry.getValue(), input); } else { putInputNode(entry.getValue(), entry.getKey()); } } return result; } @Nullable public static SNodeReference getInput(@NotNull SNode output) { return (SNodeReference) output.getUserObject(ORIGINAL_INPUT_NODE); } public static void putInput(@NotNull SNode output, SNodeReference input) { output.putUserObject(ORIGINAL_INPUT_NODE, input); } @Nullable public static SNode getInputNode(@NotNull SNode output, @NotNull SRepository repo) { // FIXME there are 3 uses of this method in quotations' QueriesGenerated, where we could use context(TQC).getOriginalCopiedInputNode // and two more (TQC and TextPreviewModel_Action) where we have access to input model, so that we could simply do // originalInputModel.getNode(ptr.getNodeId()), no need to (a) resolve through repo; (b) keep whole reference (nodeId suffice) // However, shall look into cases when original input comes from a model different than the one being generated. Perhaps, shall use // different key in that case? SNodeReference inputNodePointer = (SNodeReference) output.getUserObject(ORIGINAL_INPUT_NODE); if (inputNodePointer == null) { return null; } return inputNodePointer.resolve(repo); } public static void putInputNode(@NotNull SNode output, @NotNull SNode input) { output.putUserObject(ORIGINAL_INPUT_NODE, new SNodePointer(input)); } public static void fillOriginalNode(@NotNull SNode inputNode, @NotNull SNode outputNode, boolean originalInput) { if (originalInput) { putInputNode(outputNode, inputNode); } else { SNodeReference originalInputNode = getInput(inputNode); if (originalInputNode != null) { putInput(outputNode, originalInputNode); } } } }