/* * Copyright 2003-2016 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.generator.impl; import jetbrains.mps.generator.ModelGenerationPlan.Checkpoint; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SNodeId; import org.jetbrains.mps.openapi.model.SNodeUtil; import org.jetbrains.mps.openapi.util.TreeIterator; /** * Ensures we trace origin of node in a transient model, whether it's original input model or one of previous checkpoint steps. * Keeps traces for each node in the model during transformation phase, and once the phase is over, this trace is used to populate * label mappings information that will persist. Once mappings are ready, the trace is of no use and can be disposed (unless we decide * to use it as a replacement for {@link jetbrains.mps.textgen.trace.TracingUtil} and navigation from transient models back to original one. * * ModelTransitions is the source of TransitionTrace, and keeps track of CP-CP transitions (similar to ModelCheckpoints->CheckpointState relation, just for the * active transformation). * {@code TransitionTrace} knows last checkpoint (if any), and doesn't know next one. Now it's {@link GenPlanActiveStep} that keeps track of the plan and actual * position in there. Perhaps, that's not the most elegant approach. * * FIXME inputNode may not necessarily come from the input model, it might be arbitrary non-transient (or even perhaps checkpoint?!) model, * thus saving nodeId is not sufficient. OTOH, don't want to save SNodeReference as it's superficial in most regular cases * @author Artem Tikhomirov */ class TransitionTrace { /** * IMPLEMENTATION NOTE: * For prototype purposes, follow approach of TracingUtil, with user object pointer to origin, associated with each node. * Although I don't like untyped node.userObject and data tied to a model structure, I'm not yet confident whether per-node approach * is inherently bad, huge maps, after all, aren't nice, too. */ private static final String ORIGIN_TRACE = "originTrace"; private final Checkpoint myCheckpoint; private final ModelTransitions myModelTrace; TransitionTrace(@NotNull ModelTransitions modelTrace) { // original input model myCheckpoint = null; myModelTrace = modelTrace; } TransitionTrace(@NotNull Checkpoint checkpoint, @NotNull ModelTransitions modelTrace) { myCheckpoint = checkpoint; myModelTrace = modelTrace; } /** * Indicate all nodes of the transient model originate from identical nodes of a cloned checkpoint model * FIXME could do the same for TracingUtil.ORIGINAL_INPUT_NODE, i.e. TT.reset(transientModel@0) to make it uniform * FIXME perhaps, shall take originModel as well, and a function that maps nodes of transientModel to nodes of originalModel * (with getNodeId() as default). Otherwise connection to originalModel is not apparent here. */ void reset(@NotNull SModel transientModel) { for (SNode n : SNodeUtil.getDescendants(transientModel)) { n.putUserObject(ORIGIN_TRACE, n.getNodeId()); } } /** * @return never {@code null} */ public ModelTransitions getModelTrace() { return myModelTrace; } public boolean hasOrigin(@NotNull SNode node) { return doGet(node) instanceof SNodeId; } /*package*/ SNodeId getOrigin(@NotNull SNode node) { Object rv = doGet(node); return rv instanceof SNodeId ? (SNodeId) rv : null; } /** * Output node derives its origin from the given input node. * If output node already got an origin, it's preserved. * If input node has no trace, output node gets none as well. * No child of output node is processed. */ public void deriveOrigin(@NotNull SNode inputNode, @NotNull SNode outputNode) { if (inputNode == outputNode) { // no-op for the present approach (user object at a node), // perhaps, would change if we decide to handle in-place copied nodes in a different way (e.g. trace this fact) return; } if (hasOrigin(outputNode)) { return; } Object originNode = doGet(inputNode); doSet(outputNode, originNode); } /** * Initialize origin for a tree of nodes. Output nodes get origin unless they already got one, in this case * complete subtree is ignored (it's assumed the sub-tree already got origin assigned, no reason to override it) */ public void deriveOrigin(@NotNull SNode inputNode, @NotNull TreeIterator<SNode> outputNodes) { Object originNode = doGet(inputNode); if (!(originNode instanceof SNodeId)) { return; } while (outputNodes.hasNext()) { SNode n = outputNodes.next(); Object existing = doGet(n); if (existing instanceof SNodeId) { outputNodes.skipChildren(); continue; } doSet(n, originNode); } } private static Object doGet(SNode n) { return n.getUserObject(ORIGIN_TRACE); } private static void doSet(SNode n, Object value) { n.putUserObject(ORIGIN_TRACE, value); } }