/*
* 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.IGeneratorLogger;
import jetbrains.mps.generator.impl.query.GeneratorQueryProvider;
import jetbrains.mps.generator.impl.query.PropertyValueQuery;
import jetbrains.mps.generator.impl.query.QueryKey;
import jetbrains.mps.generator.impl.query.QueryKeyImpl;
import jetbrains.mps.generator.impl.query.ReferenceTargetQuery;
import jetbrains.mps.generator.impl.reference.PostponedReference;
import jetbrains.mps.generator.impl.reference.ReferenceInfo_Macro2;
import jetbrains.mps.generator.impl.reference.ReferenceInfo_Template;
import jetbrains.mps.generator.runtime.TemplateContext;
import jetbrains.mps.generator.runtime.TemplateExecutionEnvironment;
import jetbrains.mps.generator.template.PropertyMacroContext;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.AttributeOperations;
import jetbrains.mps.smodel.SNodePointer;
import jetbrains.mps.smodel.SNodeUtil;
import jetbrains.mps.smodel.StaticReference;
import jetbrains.mps.util.SNodeOperations;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.language.SConcept;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import org.jetbrains.mps.openapi.language.SProperty;
import org.jetbrains.mps.openapi.language.SReferenceLink;
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.SNodeAccessUtil;
import org.jetbrains.mps.openapi.model.SNodeId;
import org.jetbrains.mps.openapi.model.SNodeReference;
import org.jetbrains.mps.openapi.model.SReference;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Runtime representation of template node - element in template model that serves as sort of a pattern (factory, configurator) for output node.
* Here we evaluate template values only once and subsequently apply these prepared values.
* This class is for interpreted templates, generated code handles most of this in reduce_TemplateNode template.
* @author Artem Tikhomirov
*/
class TemplateNode {
private final SNode myNode;
private final MacroNode myFirstMacro;
private final SNodePointer myTemplateNodeReference;
private final String myTemplateNodeId;
private final SConcept myTemplateNodeConcept;
private final SContainmentLink myRoleInParent;
private Mold myMold;
/*package*/ TemplateNode(@NotNull SNode templateNode, @NotNull MacroNode.Factory macroFactory) {
myNode = templateNode;
myTemplateNodeReference = new SNodePointer(templateNode);
myTemplateNodeId = GeneratorUtil.getTemplateNodeId(templateNode);
myTemplateNodeConcept = templateNode.getConcept();
myRoleInParent = templateNode.getContainmentLink();
//
// need to build linked list of macro nodes; use stack to start from the tail
final ArrayDeque<SNode> attachedMacros = new ArrayDeque<SNode>(5);
for (SNode attrNode : templateNode.getChildren(SNodeUtil.link_BaseConcept_smodelAttribute)) {
if (RuleUtil.isNodeMacro(attrNode)) {
attachedMacros.push(attrNode);
}
}
MacroNode last = null;
while (!attachedMacros.isEmpty()) {
last = macroFactory.create(attachedMacros.pop(), this, last);
}
myFirstMacro = last;
}
/**
* configure new output node according to template
*/
public void apply(TemplateContext context, SNode outputNode) throws GenerationFailureException {
final TemplateExecutionEnvironment env = context.getEnvironment();
final TemplateGenerator generator = env.getGenerator();
if (myMold == null) {
synchronized (this) {
if (myMold == null) {
myMold = new Mold(myNode, generator, generator.getLogger());
}
}
}
myMold.moldPropertyValues(outputNode);
for (PropertyMacro pm : myMold.myMacroProperties) {
pm.expand(context, outputNode);
}
for (ReferenceMacro rm : myMold.myMacroRefs) {
rm.newPostponedReference(outputNode, context).registerWith(generator);
}
for (RefInfo r : myMold.myStaticRefs) {
// optimization for external static references (do not resolve them)
SReference newReference = new StaticReference(r.role, outputNode, r.targetModel, r.targetId, r.resolveInfo);
outputNode.setReference(r.role, newReference);
}
for (RefInfo r : myMold.myInnerRefs) {
ReferenceInfo_Template refInfo = new ReferenceInfo_Template(getTemplateNodeReference(), GeneratorUtil.getTemplateNodeId(r.targetNode), r.resolveInfo, context);
new PostponedReference(r.role, outputNode, refInfo).registerWith(generator);
}
for (RefInfo r : myMold.myOtherRefs) {
outputNode.setReferenceTarget(r.role, r.targetNode);
}
}
public MacroNode getFirstMacro() {
return myFirstMacro;
}
public SConcept getConcept() {
return myTemplateNodeConcept;
}
/**
* Children of template node that are regular template nodes (i.e. are not property/reference macros)
* FIXME would be nice to have these as collection of TemplateNode right away, but need to deal with macro handling first
* (i.e. what if child template node got a macro, how it's handled to TemplateProcessor). The drawback, however, is that
* we might initialize a huge(really? ) tree of template nodes that will never be used (i.e. with a CALL macro on a child node)
* Is it true template nodes under macros that doesn't use them (like CALL) are small so that we can ignore aforementioned pitfall?
*/
public Iterable<SNode> getChildTemplates() {
assert myMold != null : "TemplateNode shall be initialized with prior call to #apply";
return myMold.myChildTemplates;
}
public SNodeReference getTemplateNodeReference() {
return myTemplateNodeReference;
}
/**
* Identity of this template node we use to bind input and output node to template that produced them
*/
@NotNull
public String getTemplateNodeId() {
return myTemplateNodeId;
}
public SContainmentLink getRoleInParent() {
return myRoleInParent;
}
/**
* State of the template node we 'mold to' a given output node
*/
private static class Mold {
public final List<SNode> myChildTemplates;
// myTemplateProperties together with myTemplatePropertyValues gives a collection of properties to set/mold into new node
private final SProperty[] myTemplateProperties;
private final String[] myTemplatePropertyValues;
public final PropertyMacro[] myMacroProperties;
public final ReferenceMacro[] myMacroRefs;
public final RefInfo[] myStaticRefs;
public final RefInfo[] myInnerRefs;
public final RefInfo[] myOtherRefs;
private Mold(SNode templateNode, GeneratorQueryProvider.Source gqps, IGeneratorLogger log) {
final ArrayList<SProperty> propsHandledWithMacro = new ArrayList<SProperty>();
final ArrayList<SReferenceLink> refsHandledWithMacro = new ArrayList<>();
final ArrayList<SNode> templateChildNodes = new ArrayList<SNode>();
final ArrayList<PropertyMacro> propertyMacros = new ArrayList<PropertyMacro>();
final ArrayList<ReferenceMacro> refMacros = new ArrayList<>();
// property and reference macros could not yet use queries of a generator other than the one with template node,
// no need to obtain fresh GQP (namely, ReflectiveQP) instance for each macro.
final GeneratorQueryProvider queryProvider = gqps.getQueryProvider(templateNode.getReference());
// process property and reference macros
for (SNode templateChildNode : templateNode.getChildren()) {
SConcept templateChildNodeConcept = templateChildNode.getConcept();
if (RuleUtil.isTemplateLanguageElement(templateChildNodeConcept)) {
if (templateChildNodeConcept.equals(RuleUtil.concept_PropertyMacro)) {
final SProperty propertyName = AttributeOperations.getProperty(templateChildNode);
propsHandledWithMacro.add(propertyName);
SNode function = RuleUtil.getPropertyMacro_ValueFunction(templateChildNode);
QueryKey qk = new QueryKeyImpl(templateChildNode.getReference(), function.getNodeId(), templateChildNode);
final PropertyValueQuery q = queryProvider.getPropertyValueQuery(qk);
propertyMacros.add(new PropertyMacro(q, templateChildNode.getReference()));
} else if (templateChildNodeConcept.equals(RuleUtil.concept_ReferenceMacro)) {
final SReferenceLink refMacroRole = AttributeOperations.getLink(templateChildNode);
SNode function = RuleUtil.getReferenceMacro_GetReferent(templateChildNode);
if (function == null) {
log.error(templateChildNode.getReference(), "No query function for reference macro, reference would be copied as is");
continue;
}
QueryKey qk = new QueryKeyImpl(templateChildNode.getReference(), function.getNodeId());
ReferenceTargetQuery q = queryProvider.getReferenceTargetQuery(qk);
String resolveInfo = getDefaultResolveInfo(templateNode.getReferenceTarget(refMacroRole));
refsHandledWithMacro.add(refMacroRole);
refMacros.add(new ReferenceMacro(q, templateChildNode.getReference(), refMacroRole, resolveInfo));
}
} else {
templateChildNodes.add(templateChildNode);
}
}
myChildTemplates = templateChildNodes.isEmpty() ? Collections.<SNode>emptyList() : Arrays.asList(templateChildNodes.toArray(new SNode[templateChildNodes.size()]));
myMacroRefs = refMacros.toArray(new ReferenceMacro[refMacros.size()]);
myMacroProperties = propertyMacros.toArray(new PropertyMacro[propertyMacros.size()]);
final ArrayList<SProperty> templateProps = new ArrayList<SProperty>();
final ArrayList<String> templatePropValues = new ArrayList<String>();
for (SProperty p : templateNode.getProperties()) {
if (propsHandledWithMacro.contains(p)) {
continue; // property is handled with property macro
}
templateProps.add(p);
templatePropValues.add(templateNode.getProperty(p));
}
myTemplateProperties = templateProps.toArray(new SProperty[templateProps.size()]);
myTemplatePropertyValues = templatePropValues.toArray(new String[templatePropValues.size()]);
//
// prepare references
final ArrayList<RefInfo> externalStaticRefs = new ArrayList<RefInfo>();
final ArrayList<RefInfo> internalRefs = new ArrayList<RefInfo>();
final ArrayList<RefInfo> otherRefs = new ArrayList<RefInfo>();
final SModel templateModel = templateNode.getModel();
assert templateModel != null; // just to get rid of 'possible NPE' inspection. Templates always come from a model
final SModelReference templateModelReference = templateModel.getReference();
for (SReference reference : templateNode.getReferences()) {
if (refsHandledWithMacro.contains(reference.getLink())) {
// reference has been handled with the ReferenceMacro already
continue;
}
if (reference instanceof StaticReference) {
SModelReference targetModelReference = reference.getTargetSModelReference();
if (targetModelReference != null && !(templateModelReference.equals(targetModelReference))) {
// optimization for external static references (do not resolve them)
externalStaticRefs.add(new RefInfo(reference.getLink(), ((StaticReference) reference).getResolveInfo(), targetModelReference, reference.getTargetNodeId()));
continue;
}
}
SNode templateReferentNode = reference.getTargetNode();
if (templateReferentNode == null) {
String msg = "cannot resolve reference in template model; role: %s in %s";
log.error(templateNode.getReference(), String.format(msg, reference.getLink(),
SNodeOperations.getDebugText(templateNode)));
continue;
}
if (templateReferentNode.getModel() == templateModel) { // internal reference
// XXX same code is in TEEI.resolveInTemplateLater, needs refactoring
String resolveInfo = SNodeOperations.getResolveInfo(templateReferentNode);
// The right way to get string representation of the reference (aka resolveInfo) is to ask scope about it
// However, it doesn't work now (e.g. regenerate BL fails with NodeCastException in VisibleClassConstructorScope:59,
// String resolveInfo = ModelConstraints.getScope(reference).getReferenceText(reference.getSourceNode(), templateReferentNode);
internalRefs.add(new RefInfo(reference.getLink(), resolveInfo, templateReferentNode));
} else {
otherRefs.add(new RefInfo(reference.getLink(), null, templateReferentNode));
}
}
myStaticRefs = externalStaticRefs.toArray(new RefInfo[externalStaticRefs.size()]);
myInnerRefs = internalRefs.toArray(new RefInfo[internalRefs.size()]);
myOtherRefs = otherRefs.toArray(new RefInfo[otherRefs.size()]);
}
public void moldPropertyValues(SNode outputNode) {
// jetbrains.mps.util.SNodeOperations.copyProperties(myTemplateNode, outputNode);
for (int i = 0; i < myTemplateProperties.length; i++) {
outputNode.setProperty(myTemplateProperties[i], myTemplatePropertyValues[i]);
}
}
/**
* FIXME why don't we look into IResolveInfo in case target is instanceOf?
* Now it's kept the way it used to be in MacroResolver for years
*/
private static String getDefaultResolveInfo(SNode templateTargetNode) {
return templateTargetNode != null ? templateTargetNode.getName() : null;
}
}
/**
* Captures details about references outgoing from template node
*/
private static class RefInfo {
public final SReferenceLink role;
public final String resolveInfo;
public final SNode targetNode;
public final SModelReference targetModel;
public final SNodeId targetId;
public RefInfo(SReferenceLink role, String resolveInfo, SNode targetNode) {
this.role = role;
this.resolveInfo = resolveInfo;
this.targetNode = targetNode;
this.targetModel = null;
this.targetId = null;
}
public RefInfo(SReferenceLink role, String resolveInfo, SModelReference targetModel, SNodeId targetId) {
this.role = role;
this.resolveInfo = resolveInfo;
this.targetNode = null;
this.targetModel = targetModel;
this.targetId = targetId;
}
}
/**
* Captures static values of a property macro
*/
private static class PropertyMacro {
private final PropertyValueQuery myQuery;
private final SNodeReference myMacro;
private final String myTemplateValue;
public PropertyMacro(PropertyValueQuery query, SNodeReference macro) {
myQuery = query;
myMacro = macro;
final Object tv = query.getTemplateValue();
myTemplateValue = tv == null ? null : String.valueOf(tv);
}
public void expand(TemplateContext context, SNode outputNode) throws GenerationFailureException {
PropertyMacroContext pmc = new PropertyMacroContext(context, myTemplateValue, myMacro);
Object macroValue = context.getEnvironment().getQueryExecutor().evaluate(myQuery, pmc);
String propertyValue = macroValue == null ? null : String.valueOf(macroValue);
SNodeAccessUtil.setProperty(outputNode, myQuery.getProperty(), propertyValue);
}
}
/**
* Captures static values of a reference macro
*/
private static class ReferenceMacro {
private final ReferenceTargetQuery myQuery;
private final SNodeReference myMacro;
private final SReferenceLink myAssociation;
private final String myDefaultResolveInfo;
public ReferenceMacro(ReferenceTargetQuery query, SNodeReference macro, SReferenceLink associationLink, String defaultResolveInfo) {
myQuery = query;
myMacro = macro;
myAssociation = associationLink;
myDefaultResolveInfo = defaultResolveInfo;
}
public PostponedReference newPostponedReference(SNode outputNode, TemplateContext templateContext) {
ReferenceInfo_Macro2 refInfo = new ReferenceInfo_Macro2(myQuery, templateContext, myMacro, myDefaultResolveInfo);
return new PostponedReference(myAssociation, outputNode, refInfo);
}
}
}