/*
* 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.GenerationCanceledException;
import jetbrains.mps.generator.GenerationTrace;
import jetbrains.mps.generator.GenerationTracerUtil;
import jetbrains.mps.generator.runtime.TemplateContext;
import jetbrains.mps.generator.runtime.TemplateExecutionEnvironment;
import jetbrains.mps.generator.template.ITemplateProcessor;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import jetbrains.mps.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import org.jetbrains.mps.openapi.model.SNode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* Container for Template Fragments, collects them and applies through supplied TemplateProcessor.
* <p>For weave rule/macro there's {@link jetbrains.mps.generator.impl.WeaveTemplateContainer} counterpart.
* @author Artem Tikhomirov
*/
public class TemplateContainer extends RuleConsequenceProcessor {
private final SNode myTemplateNode;
private List<Pair<SNode, String>> myNodeAndMappingNamePairs;
public TemplateContainer(@NotNull SNode templateContainer) {
myTemplateNode = templateContainer;
}
public TemplateContainer(@NotNull Pair<SNode, String> fragment) {
myTemplateNode = null;
myNodeAndMappingNamePairs = Collections.singletonList(fragment);
}
/*
* Initialize container once for a template, then apply multiple times.
*/
private void initialize() throws TemplateProcessingFailureException {
if (myNodeAndMappingNamePairs != null) {
return;
}
List<SNode> fragments = checkAdjacentFragments();
List<Pair<SNode, String>> result = new ArrayList<Pair<SNode, String>>(fragments.size());
for (SNode fragment : fragments) {
result.add(new Pair<SNode, String>(SNodeOperations.getParent(fragment), GeneratorUtilEx.getMappingName_TemplateFragment(fragment, null)));
}
myNodeAndMappingNamePairs = result;
}
@NotNull
@Override
public List<SNode> processRuleConsequence(@NotNull TemplateContext ctx)
throws GenerationFailureException, DismissTopMappingRuleException, GenerationCanceledException {
initialize();
ArrayList<SNode> outputNodes = new ArrayList<SNode>();
final TemplateExecutionEnvironment environment = ctx.getEnvironment();
final GenerationTrace tracer = environment.getTrace();
ITemplateProcessor templateProcessor = environment.getTemplateProcessor();
for (Pair<SNode, String> nodeAndMappingNamePair : myNodeAndMappingNamePairs) {
SNode templateNode = nodeAndMappingNamePair.o1;
String innerMappingName = nodeAndMappingNamePair.o2;
List<SNode> _outputNodes = templateProcessor.apply(templateNode, ctx.subContext(innerMappingName));
SNode input = ctx.getInput();
tracer.trace(input == null ? null : input.getNodeId(), GenerationTracerUtil.translateOutput(_outputNodes), templateNode.getReference());
outputNodes.addAll(_outputNodes);
}
return outputNodes;
}
@NotNull
private List<SNode> checkAdjacentFragments() throws TemplateProcessingFailureException {
List<SNode> fragments = GeneratorUtilEx.getTemplateFragments(myTemplateNode);
if (fragments.isEmpty()) {
throw new TemplateProcessingFailureException(myTemplateNode, "couldn't process template: no template fragments found");
}
if (fragments.size() > 1) {
// GeneratorUtilEx.getTemplateFragments() shall not return null
Iterator<SNode> it = fragments.iterator();
SNode fragmentParent = it.next().getParent();
assert fragmentParent != null; // free-floating fragment would be odd
final SNode commonParent = fragmentParent.getParent();
final SContainmentLink role = fragmentParent.getContainmentLink();
while (it.hasNext()) {
fragmentParent = it.next().getParent();
assert fragmentParent != null; // free-floating fragment would be odd
// it's parent template that specifies context node and its role where these template fragments would get injected into,
// thus we check there's no assumption context node is different for fragments, and that they do not assume they may end up in distinct roles.
// Technically, provided ITemplateProcessor.apply() would yield something extra but SNode, we could answer with Pair(SContainmentLink,SNode)
// and inject TF outcome into different roles (see https://youtrack.jetbrains.com/issue/MPS-23373). However, this would compromise COPY-SRC
// idea (it's attached to a distinct role), and we'd need something more general, like <<apply-templates/>> to handle all children
if (commonParent != fragmentParent.getParent() || !role.equals(fragmentParent.getContainmentLink())) {
String msg = "Couldn't process template: all template fragments must reside in the same parent node. Roles: expected %s, met %s";
throw new TemplateProcessingFailureException(myTemplateNode, String.format(msg, role, fragmentParent.getContainmentLink().getName()));
}
}
}
return fragments;
}
}