/*
* Copyright 2003-2015 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.plan;
import jetbrains.mps.generator.impl.RuleUtil;
import jetbrains.mps.smodel.BootstrapLanguages;
import jetbrains.mps.smodel.FastNodeFinder;
import jetbrains.mps.smodel.FastNodeFinderManager;
import jetbrains.mps.smodel.SNodeUtil;
import jetbrains.mps.smodel.adapter.MetaAdapterByDeclaration;
import jetbrains.mps.util.annotation.ToRemove;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.language.SAbstractConcept;
import org.jetbrains.mps.openapi.language.SConcept;
import org.jetbrains.mps.openapi.language.SLanguage;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SReference;
import org.jetbrains.mps.util.Condition;
import org.jetbrains.mps.util.DescendantsTreeIterator;
import org.jetbrains.mps.util.TreeFilterIterator;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* TemplateModelScanner done right.
*
* Facility to analyze generator model and collect set of target languages along with set of languages in generator queries
* <pre>
* new ModelScanner().scan(modelA).scan(modelB).getTargetLanguages();
* </pre>
*
* @author Artem Tikhomirov
*/
public final class ModelScanner {
private final Set<SLanguage> myTargetLanguages = new HashSet<SLanguage>();
private final Set<SLanguage> myQueryLanguages = new HashSet<SLanguage>();
public ModelScanner() {
}
public Set<SLanguage> getTargetLanguages() {
return myTargetLanguages;
}
public Set<SLanguage> getQueryLanguages() {
return myQueryLanguages;
}
/**
* Compatibility code, do not use.
* Legacy TemplateModelScanner used to add j.m.lang.generator to query languages if there's at least one node in template model,
* likely to force generation of QueriesGenerated. Shall check if everything is ok if QueriesGenerated is missing,
* but templates/rules are present (e.g. there might be an MC with a drop rule that doesn't require any query).
* Also shall check generated generators case, if they are ok (i.e. TemplateModel or whatever else needed get generated)
*
* Another trick TemplateModelScanner used to do is to drop lang.generator from target languages, likely as a quick-n-dirty
* way not to fail in case there are errors in the scanner. Generally, lang.generator shall not pop up in target languages,
* provided there are no errors in the TMS implementation, and we are not targeting template language.
*
* Drop once there's no use of this method.
*/
@ToRemove(version = 0)
public ModelScanner scanInLegacyMode(SModel model) {
scan(model);
if (model.getRootNodes().iterator().hasNext()) {
myQueryLanguages.add(BootstrapLanguages.getGeneratorLang());
}
return this;
}
public ModelScanner scan(SModel model) {
// assert SModelStereotype.isGeneratorModel(model);
FastNodeFinder fnf = FastNodeFinderManager.get(model);
Translate<SNode, SNode> parentExtractor = new Translate<SNode, SNode>() {
@Override
public SNode translate(SNode t) {
return t.getParent();
}
};
Translate<SNode, SNode> inlineTemplateExtractor = new Translate<SNode, SNode>() {
@Override
public SNode translate(SNode rc) {
return RuleUtil.getInlineTemplate_templateNode(rc);
}
};
processTemplateNode(fnf.getNodes(RuleUtil.concept_TemplateFragment, false), parentExtractor);
processTemplateNode(fnf.getNodes(RuleUtil.concept_RootTemplateAnnotation, false), parentExtractor);
processTemplateNode(fnf.getNodes(RuleUtil.concept_InlineTemplate_RuleConsequence, false), inlineTemplateExtractor);
//
// Mapping scripts: pre/readonly - to queries only, modify - both to queries and templateNodes if create anything
// XXX What about utility models with change operations?
//
scanScriptsForChangeOperations(fnf);
// MappingScript_CodeBlock is subclass of TemplateQueryBase
processQueryNodes(fnf.getNodes(RuleUtil.concept_TemplateQueryBase, true));
//
// if we use patterns (e.g, in pattern reduction rule), add appropriate language then
if (!fnf.getNodes(RuleUtil.concept_PatternExpression, true).isEmpty()) {
myQueryLanguages.add(RuleUtil.getPatternLanguage());
}
return this;
}
private void processTemplateNode(Collection<SNode> templateNodes, Translate<SNode, SNode> t) {
final NodeScanner ns = new NodeScanner(new MacroFilter());
for (SNode tn : templateNodes) {
ns.scanStructure(t.translate(tn));
}
myTargetLanguages.addAll(ns.getUsedLanguages());
}
private void processQueryNodes(Iterable<SNode> nodes) {
final NodeScanner ns = new NodeScanner();
for (SNode n : nodes) {
ns.scanStructure(n);
}
myQueryLanguages.addAll(ns.getUsedLanguages());
}
private void scanScriptsForChangeOperations(FastNodeFinder fnf) {
NodeScanner refScanner = new NodeScanner();
for (SConcept modelChangeOperation : RuleUtil.getModelChangeOperations()) {
// We try to be as specific as possible and to look for particular instantiated concepts referenced from within change operation
//
for (SNode op : fnf.getNodes(modelChangeOperation, true)) {
// MappingScripts are root nodes
SNode rootWithChangeOp = op.getContainingRoot();
if (!RuleUtil.concept_MappingScript.equals(rootWithChangeOp.getConcept())) {
continue;
}
if (RuleUtil.getMappingScript_IsPreProcess(rootWithChangeOp) && !RuleUtil.getMappingScript_ModifiesModel(rootWithChangeOp)) {
continue;
}
// post-processing or pre-processing script that modifies model
refScanner.scanReferences(op);
}
}
myTargetLanguages.addAll(refScanner.getUsedLanguages());
}
private interface Translate<T1, T2> {
T2 translate(T1 t);
}
/**
* Facility to collect meta-dependencies (used concepts and languages) of a node hierarchy or a collection of nodes.
* <pre>
* new NodeScanner().scanStructure(nodeA).scanReferences(nodeB).getUsedLanguages();
* </pre>
*/
private static final class NodeScanner {
private final Condition<SNode> myCondition;
private final Set<SAbstractConcept> myConceptsInUse = new HashSet<SAbstractConcept>();
private Set<SLanguage> myLanguagesInUse;
public NodeScanner() {
myCondition = null;
}
public NodeScanner(@Nullable Condition<SNode> condition) {
myCondition = condition;
}
/**
* Collect meta-dependencies of the node, including all its children
*/
public NodeScanner scanStructure(@NotNull SNode node) {
myLanguagesInUse = null;
for (Iterator<SNode> it = getNodeIterator(node); it.hasNext(); ) {
SNode n = it.next();
myConceptsInUse.add(n.getConcept());
}
return this;
}
/**
* Collect meta-dependencies of references from the node and its children
*/
public NodeScanner scanReferences(@NotNull SNode node) {
myLanguagesInUse = null;
for (Iterator<SNode> it = getNodeIterator(node); it.hasNext(); ) {
SNode n = it.next();
for (SReference r : n.getReferences()) {
// we could have checked here r.getLink().getTargetConcept().isSubConceptOf(SNodeUtil.concept_AbstractConceptDeclaration)
// to filter out 'meta' links, but does it pay off? We need target node anyway to figure out particular concept
final SNode tn = r.getTargetNode();
if (tn == null) {
continue;
}
SConcept targetNodeConcept = tn.getConcept();
if (SNodeUtil.concept_InterfaceConceptDeclaration.equals(targetNodeConcept) || SNodeUtil.concept_ConceptDeclaration.equals(targetNodeConcept)) {
// n points with r to a concept node tn, shall record concept represented by tn as used
// e.g. nlist<Expression>, SNodeListType#elementConcept:AbstractConceptDeclaration, tn would be ConceptDeclaration "Expression"
myConceptsInUse.add(MetaAdapterByDeclaration.getConcept(tn));
}
}
}
return this;
}
private Iterator<SNode> getNodeIterator(SNode node) {
return myCondition == null ? new DescendantsTreeIterator(node) : new TreeFilterIterator<SNode>(new DescendantsTreeIterator(node), myCondition);
}
public Set<SLanguage> getUsedLanguages() {
if(myLanguagesInUse == null) {
final HashSet<SLanguage> usedLanguages = new HashSet<SLanguage>(myConceptsInUse.size());
for (SAbstractConcept conceptInUse : myConceptsInUse) {
final SLanguage language = conceptInUse.getLanguage();
usedLanguages.add(language);
}
myLanguagesInUse = usedLanguages;
}
return myLanguagesInUse;
}
}
// walk hierarchy of nodes, excluding template macros
// FIXME there are certain macros which templateNode we don't need to look at (e.g. COPY-SRC)
private static class MacroFilter implements Condition<SNode> {
@Override
public boolean met(SNode node) {
if (SNodeUtil.link_BaseConcept_smodelAttribute.equals(node.getContainmentLink())) {
return RuleUtil.isTemplateLanguageElement(node);
}
return false;
}
}
}