/*
* Copyright 2003-2017 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.impl.cache.MappingsMemento;
import jetbrains.mps.generator.impl.plan.CheckpointState;
import jetbrains.mps.generator.plan.CheckpointIdentity;
import jetbrains.mps.smodel.ModelDependencyUpdate;
import jetbrains.mps.smodel.ModelImports;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SModelReference;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeId;
import java.util.Collection;
import java.util.Collections;
/**
* Translate information about mapping labels known at checkpoint step to read-only, persisted (even though memory-only now) {@link CheckpointState state}
* @author Artem Tikhomirov
* @since 3.3
*/
class CheckpointStateBuilder {
// FIXME refactor/replace MappingsMemento with more sophisticated storage, with ML objects instead of Map/String/Object.
// Shall support origins other than coming from previous inputModel (either original or checkpoint) - i.e. xModel references to unrelated models
private final MappingsMemento myMemento;
private final SModelReference myOutputModel;
private final TransitionTrace myTransitionTrace;
private final SModel myTransientModel;
private final SModel myCheckpointModel;
private boolean myCloneDone = false;
public CheckpointStateBuilder(@NotNull SModel transientModel, @NotNull SModel blankCheckpointModel, @NotNull TransitionTrace transitionTrace) {
myTransientModel = transientModel;
myCheckpointModel = blankCheckpointModel;
myTransitionTrace = transitionTrace;
myMemento = new MappingsMemento();
myOutputModel = transientModel.getReference();
}
public void record(SNode inputNode, String mappingLabel, SNode outputNode) {
SNodeId origin = getInputOrigin(inputNode);
if (origin == null) {
return;
}
/*
FIXME it's possible for outputNode to belong to another model than the transient one supplied at construction:
e.g. when there's post-processing step right before the CP. In this case, most labels would point to a model
that was input for the post-processing step (most, but not all as it's possible to register MLs through genContext.register from scripts)
Hence this assertion prevented us from employing GPs in some scenarios. However, I don't feel it's completely useless, perhaps,
we shall fix it another way round, with MLs updated to point to right transient model.
assert myOutputModel.equals(outputNode.getModel().getReference());
*/
// FIXME here we assume checkpoint model is cloned with nodeId of outputNode kept.
myMemento.addOutputNodeByInputNodeAndMappingName(origin, mappingLabel, outputNode);
}
public void record(SNode inputNode, String mappingLabel, Collection<SNode> outputNodes) {
SNodeId origin = getInputOrigin(inputNode);
if (origin == null) {
return;
}
/*
FIXME see record() above
for (SNode o : outputNodes) {
assert myOutputModel.equals(o.getModel().getReference());
}
*/
// FIXME here we assume checkpoint model is cloned with nodeId of outputNode kept.
myMemento.addOutputNodeByInputNodeAndMappingName(origin, mappingLabel, outputNodes);
}
public void record(String mappingLabel, SNode outputNode) {
myMemento.addNewOutputNode(mappingLabel, outputNode.getNodeId());
}
// FIXME add similar operation to get true identity of output node(s). Now MappingsMemento implicitly assumes (with outputNode.getNodeId() call)
// identity of nodes in checkpoint model match that in transient/output model
private SNodeId getInputOrigin(SNode inputNode) {
// FIXME shall record identity of input model in a way it could be referenced from outside
// (i.e. either as regular model or another checkpoint model). E.g. pair (previous cp model + node id).
// For the time being, however, consider regular model as possible input (last cp) only,
// do not track 'previous' checkpoint and therefore can reuse MappingsMemento.
return myTransitionTrace.getOrigin(inputNode);
}
/**
* optional operation, to record generator mappings in the state being built.
* Invoke prior to {@link #create(CheckpointIdentity)}
* Optional, for a hypothetical (didn't check/think over too much) scenario when CP comes as the very first step.
*
* @param originalInputModel non-null
* @param stepLabels non-null
*/
/*package*/ void addMappings(SModel originalInputModel, GeneratorMappings stepLabels) {
// FIXME likely, GeneratorMappings shall care about MappingMemento only (pass TransitionTrace there as well).
stepLabels.export(this);
// IMPORTANT need to create CP model first, as DebugMappingsBuilder need cloned nodes to substitute
// reference targets from transient model to that in CP model (see DMB.substitute)
cloneTransientToCheckpoint();
new ModelImports(myCheckpointModel).addModelImport(originalInputModel.getReference());
DebugMappingsBuilder dmb = new DebugMappingsBuilder(originalInputModel.getRepository(), Collections.singletonMap(myTransientModel, myCheckpointModel));
SNode debugMappings = dmb.build(myCheckpointModel, stepLabels);
myCheckpointModel.addRootNode(debugMappings);
}
/*package*/ CheckpointState create(CheckpointIdentity step) {
cloneTransientToCheckpoint();
return new CheckpointState(myMemento, myCheckpointModel, step);
}
private void cloneTransientToCheckpoint() {
if (!myCloneDone) {
new CloneUtil(myTransientModel, myCheckpointModel).cloneModel();
// ReferenceResolvers could have added references to nodes in other checkpoint models, we need to propagate these
// dependencies into imports to ensure subsequent module.forget() could find and clear all dependant models as well
// RR would not change list of languages, hence no updateUsedLanguages() call. And we don't care about explicit
// imports of language's accessory models either.
new ModelDependencyUpdate(myCheckpointModel).updateImportedModels(null);
myCloneDone = true;
}
}
}