/* * 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; } } }