/*
* 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.GenerationCanceledException;
import jetbrains.mps.generator.GenerationTrace;
import jetbrains.mps.generator.GenerationTracerUtil;
import jetbrains.mps.generator.IGeneratorLogger;
import jetbrains.mps.generator.impl.query.GeneratorQueryProvider;
import jetbrains.mps.generator.impl.reference.PostponedReference;
import jetbrains.mps.generator.impl.reference.ReferenceInfo_Macro;
import jetbrains.mps.generator.impl.reference.ReferenceInfo_Template;
import jetbrains.mps.generator.runtime.GenerationException;
import jetbrains.mps.generator.runtime.NodePostProcessor;
import jetbrains.mps.generator.runtime.NodeWeaveFacility;
import jetbrains.mps.generator.runtime.NodeWeaveFacility.WeaveContext;
import jetbrains.mps.generator.runtime.ReferenceResolver;
import jetbrains.mps.generator.runtime.TemplateContext;
import jetbrains.mps.generator.runtime.TemplateDeclaration;
import jetbrains.mps.generator.runtime.TemplateExecutionEnvironment;
import jetbrains.mps.generator.runtime.TemplateModel;
import jetbrains.mps.generator.runtime.TemplateReductionRule;
import jetbrains.mps.generator.runtime.TemplateRuleWithCondition;
import jetbrains.mps.generator.runtime.TemplateSwitchMapping;
import jetbrains.mps.generator.template.ITemplateProcessor;
import jetbrains.mps.generator.template.QueryExecutionContext;
import jetbrains.mps.smodel.CopyUtil;
import jetbrains.mps.smodel.IOperationContext;
import jetbrains.mps.textgen.trace.TracingUtil;
import jetbrains.mps.util.containers.ConcurrentHashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.language.SConcept;
import org.jetbrains.mps.openapi.language.SReferenceLink;
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.SNodeReference;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* Evgeny Gryaznov, 11/10/10
*/
public class TemplateExecutionEnvironmentImpl implements TemplateExecutionEnvironment {
private final TemplateGenerator generator;
private final QueryExecutionContext myExecutionContext;
private final ITemplateProcessor myTemplateProcessor;
private final ReductionTrack myReductionTrack;
// although it's possible to instantiate ReductionTrack here (we've got generator in TP),
// I plan to separate TEE and RT so that they are independent
public TemplateExecutionEnvironmentImpl(@NotNull TemplateProcessor templateProcessor, @NotNull QueryExecutionContext executionContext, @NotNull ReductionTrack reductionTrack) {
this.generator = templateProcessor.getGenerator();
myExecutionContext = executionContext;
myTemplateProcessor = templateProcessor;
myReductionTrack = reductionTrack;
}
@Override
public IOperationContext getOperationContext() {
return generator.getGeneratorSessionContext();
}
@Override
public SModel getOutputModel() {
return generator.getOutputModel();
}
@NotNull
@Override
public SNode createOutputNode(@NotNull SConcept concept) {
return generator.getOutputModel().createNode(concept);
}
@NotNull
@Override
public TemplateGenerator getGenerator() {
return generator;
}
@NotNull
@Override
public GenerationTrace getTrace() {
return generator.getTrace();
}
@Override
public IGeneratorLogger getLogger() {
return generator.getLogger();
}
@NotNull
@Override
public GeneratorQueryProvider getQueryProvider(@NotNull SNodeReference ruleNode) {
return generator.getQueryProvider(ruleNode);
}
@NotNull
@Override
public QueryExecutionContext getQueryExecutor() {
return myExecutionContext;
}
@NotNull
@Override
public ITemplateProcessor getTemplateProcessor() {
return myTemplateProcessor;
}
@Override
@NotNull
public List<SNode> copyNodes(@NotNull Iterable<SNode> inputNodes, @NotNull SNodeReference templateNode, @NotNull String templateId,
@NotNull TemplateContext ctx) throws GenerationCanceledException, GenerationFailureException {
List<SNode> outputNodes = generator.copyNodes(inputNodes, ctx, templateId, this);
if (!outputNodes.isEmpty()) {
new ChildAdopter(generator).checkIsExpectedLanguage(outputNodes, templateNode, ctx);
}
return outputNodes;
}
@Override
public SNode insertNode(SNode child, SNodeReference templateNode, TemplateContext templateContext) throws GenerationCanceledException, GenerationFailureException {
ChildAdopter a = new ChildAdopter(generator);
a.checkIsExpectedLanguage(Collections.singletonList(child), templateNode, templateContext);
return a.adopt(child, templateContext);
}
@Nullable
@Override
public Collection<SNode> trySwitch(SNodeReference _switch, TemplateContext context) throws GenerationException {
FastRuleFinder rf = generator.getRuleManager().getSwitchRules(_switch);
Collection<SNode> outputNodes = tryToReduce(rf, context);
if (outputNodes != null) {
// XXX it seems odd we do not do TracingUtil.fillOriginalNode(context.getInput(), outputNodes.get(0), false)
// to record actual origin for the switch outcome. This ruins scenario like
// Rule X -> $SWITCH$ for x.refY
// where X is recorded as origin for whatever outcome SWITCH reports.
// However, if we do fill proper original node here, this ruins existing scenario, where bl.collections got rules
// for AbstractCreator, which switch by type of the creator, so that generated new LingHashSet() points not to 'new linkhashset<>'
// creator node, but to value of ((TypeDerivable) parent).deriveType, which might be field declaration
if (outputNodes.size() == 1 && context.getInputName() != null) {
SNode reducedNode = outputNodes.iterator().next();
// register copied node
generator.registerMappingLabel(context.getInput(), context.getInputName(), reducedNode);
}
generator.recordTransformInputTrace(context.getInput(), outputNodes);
return outputNodes;
}
// try the default case
TemplateSwitchMapping current = generator.getSwitch(_switch);
if (current != null) {
outputNodes = current.applyDefault(this, _switch, context.getInputName(), context); // FIXME TSM.applyDefault without explicit mappingLabel
generator.recordTransformInputTrace(context.getInput(), outputNodes);
return outputNodes;
}
// no switch-case found for the inputNode - continue with templateNode under the $switch$
return null;
}
@Override
public Collection<SNode> applyTemplate(@NotNull SNodeReference templateDeclaration, @NotNull SNodeReference templateNode, @NotNull TemplateContext context, Object... arguments) throws GenerationException {
TemplateDeclaration templateDeclarationInstance = loadTemplateDeclaration(templateDeclaration, templateNode, context, arguments);
if (templateDeclarationInstance == null) {
return Collections.emptyList();
}
return templateDeclarationInstance.apply(this, context);
}
/*package*/ TemplateDeclaration loadTemplateDeclaration(@NotNull SNodeReference templateDeclaration, @NotNull SNodeReference templateNode, @NotNull TemplateContext context, Object... arguments) {
TemplateModel templateModel = generator.getGenerationPlan().getTemplateModel(templateDeclaration.getModelReference());
TemplateDeclaration templateDeclarationInstance = templateModel == null ? null : templateModel.loadTemplate(templateDeclaration, arguments);
if (templateModel == null || templateDeclarationInstance == null) {
String msg = "%s not found: cannot apply template declaration, try to check & regenerate affected generators";
getLogger().error(templateNode, String.format(msg, templateModel == null ? "template model" : "declaration"),
GeneratorUtil.describeIfExists(context.getInput(), "input"),
GeneratorUtil.describe(templateNode, "template"),
GeneratorUtil.describe(templateDeclaration, "template declaration"));
return null;
}
return templateDeclarationInstance;
}
@Override
public void nodeCopied(TemplateContext context, SNode outputNode, String templateNodeId) {
generator.nodeCopied(context, outputNode, templateNodeId);
}
@Override
public void registerLabel(SNode inputNode, SNode outputNode, String mappingLabel) {
generator.registerMappingLabel(inputNode, mappingLabel, outputNode);
}
@Override
public void registerLabel(SNode inputNode, Iterable<SNode> outputNodes, String mappingLabel) {
for (SNode outputNode : outputNodes) {
generator.registerMappingLabel(inputNode, mappingLabel, outputNode);
}
}
@Override
public void resolveInTemplateLater(@NotNull SNode outputNode, @NotNull SReferenceLink role, SNodeReference sourceNode, String templateNodeId, String resolveInfo, TemplateContext context) {
ReferenceInfo_Template refInfo = new ReferenceInfo_Template(sourceNode, templateNodeId, resolveInfo, context);
new PostponedReference(role, outputNode, refInfo).registerWith(generator);
}
@Override
public void resolve(@NotNull ReferenceResolver resolver) {
ReferenceInfo_Macro refInfo = new ReferenceInfo_Macro(resolver);
new PostponedReference(resolver.getReferenceRole(), resolver.getOutputNode(), refInfo).registerWith(generator);
}
@Override
public void postProcess(@NotNull NodePostProcessor postProcessor) {
generator.getDelayedChanges().add(postProcessor);
}
@NotNull
@Override
public NodeWeaveFacility prepareWeave(@NotNull WeaveContext context, @NotNull SNodeReference templateNode) {
return new NodeWeaveSupport(context, templateNode, this);
}
// Internal API, perhaps, shall be part of ExecutionEnvironmentInternal iface
void blockReductionsForCopiedNode(SNode inputNode, SNode outputNode) {
myReductionTrack.blockReductionsForCopiedNode(inputNode, outputNode);
}
@Nullable
Collection<SNode> tryToReduce(@NotNull SNode inputNode) throws GenerationFailureException, GenerationCanceledException {
FastRuleFinder rf = generator.getRuleManager().getReductionRules();
Collection<SNode> outputNodes = tryToReduce(rf, new DefaultTemplateContext(this, inputNode, null));
if (outputNodes != null) {
if (outputNodes.size() == 1) {
// [artem] I have no idea why same mappings are not done for switch, but it's the way it goes from rev d552b27
SNode reducedNode = outputNodes.iterator().next();
// output node should be accessible via 'findCopiedNode'
generator.addCopiedOutputNodeForInputNode(inputNode, reducedNode);
// preserve user objects
if (TracingUtil.getInput(reducedNode) == null) {
CopyUtil.copyUserObjects(inputNode, reducedNode);
// keep track of 'original input node'
TracingUtil.fillOriginalNode(inputNode, reducedNode, false);
}
}
generator.recordTransformInputTrace(inputNode, outputNodes);
return outputNodes;
}
return null;
}
protected final Set<SNodeReference> myFailedRules = new ConcurrentHashSet<SNodeReference>();
/*
* returns null if no reductions found
*/
@Nullable
Collection<SNode> tryToReduce(FastRuleFinder<TemplateReductionRule> ruleFinder, @NotNull TemplateContext context) throws GenerationFailureException, GenerationCanceledException {
final SNode inputNode = context.getInput();
TemplateReductionRule reductionRule = null;
// find rule
List<TemplateReductionRule> conceptRules = ruleFinder.findReductionRules(inputNode);
if (conceptRules == null) {
return null;
}
try {
for (TemplateReductionRule rule : conceptRules) {
reductionRule = rule;
if (!myReductionTrack.isReductionBlocked(inputNode, rule)) {
if (rule instanceof TemplateRuleWithCondition) {
if (!getQueryExecutor().isApplicable((TemplateRuleWithCondition) rule, context)) {
continue;
}
// fall-through
}
try {
myReductionTrack.enter(inputNode, rule);
Collection<SNode> outputNodes = getQueryExecutor().applyRule(rule, context);
if (outputNodes != null) {
SNodeId in = context.getInput() == null ? null : context.getInput().getNodeId();
getTrace().trace(in, GenerationTracerUtil.translateOutput(outputNodes), rule.getRuleNode());
generator.copyNodeAttributes(context, outputNodes, this);
return outputNodes;
}
} catch (DismissTopMappingRuleException ex) {
// it's ok, just continue with a next applicable rule, if any
generator.reportDismissRuleException(ex, reductionRule);
} finally {
myReductionTrack.leave();
}
}
}
} catch (AbandonRuleInputException ex) {
return Collections.emptyList();
} catch (TemplateProcessingFailureException ex) {
SNodeReference ruleNode = reductionRule.getRuleNode();
if (myFailedRules.add(ruleNode)) {
getLogger().error(ruleNode, String.format("Reduction rule failed: %s", ex.getMessage()), ex.asProblemDescription());
}
} catch (GenerationFailureException ex) {
throw ex;
} catch (GenerationCanceledException ex) {
throw ex;
} catch (GenerationException ex) {
// ignore
// shall not happen provided we know all subclasses of GenerationException.
// XXX why log? perhaps, could throw as GenerationFailureException? Meanwhile, did what TemplateGenerator#applyCreateRoot does
getLogger().error(reductionRule == null ? null : reductionRule.getRuleNode(), "internal error: " + ex.toString());
}
return null;
}
}