/* * 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.plan; import jetbrains.mps.generator.runtime.TemplateModule; import jetbrains.mps.smodel.language.GeneratorRuntime; import jetbrains.mps.smodel.language.LanguageRegistry; import jetbrains.mps.smodel.language.LanguageRuntime; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.language.SLanguage; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.module.SModuleReference; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Queue; import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; /** * Find out generators for a model according to set of languages model actually uses. * NOTE, this is internal facility and NOT AN API. Made public for the sake of debug/info actions. * @author Artem Tikhomirov */ public final class EngagedGeneratorCollector { private static final Logger LOG = LogManager.getLogger(GenerationPlan.class); @NotNull private final SModel myModel; private final List<SLanguage> myAdditionalLanguages; private Collection<SLanguage> myDirectLangUse; private Collection<TemplateModule> myEngagedGenerators; private final Set<SLanguage> myBadLanguages = new HashSet<>(); // all generators found during the process, with possible duplicates // e.g. L1 with G1 and L2 with G2, both G1 and G2 extend G3, which would show up twice in this case private final List<EngagedGenerator> myEngagedTrace = new ArrayList<>(); public EngagedGeneratorCollector(@NotNull SModel model, @Nullable Collection<SLanguage> additionalLanguages) { myModel = model; myAdditionalLanguages = additionalLanguages == null ? Collections.emptyList() : new ArrayList<>(additionalLanguages); } /** * @return list of languages actually used in the model, including those specified with 'engaged on generation' model property. */ public Collection<SLanguage> getDirectlyUsedLanguages() { if (myDirectLangUse == null) { myDirectLangUse = ModelContentUtil.getUsedLanguages(myModel); } return myDirectLangUse; } /** * @return list of used languages including additional languages supplied {@linkplain EngagedGeneratorCollector externally} (if any) */ public Collection<SLanguage> getAllLanguages() { if (myAdditionalLanguages.isEmpty()) { return getDirectlyUsedLanguages(); } Collection<SLanguage> l1 = getDirectlyUsedLanguages(); ArrayList<SLanguage> rv = new ArrayList<>(l1.size() + myAdditionalLanguages.size()); rv.addAll(l1); rv.addAll(myAdditionalLanguages); return rv; } public Collection<TemplateModule> getGenerators() { if (myEngagedGenerators == null) { myEngagedGenerators = build(); } return myEngagedGenerators; } @NotNull private Collection<TemplateModule> build() { myBadLanguages.clear(); myEngagedTrace.clear(); final Collection<SLanguage> initialLanguages = getAllLanguages(); Queue<EngagedLanguage> queue = new ArrayDeque<>(resolveLanguages(initialLanguages, null, null)); // XXX could have used Set<SLanguage> here, but it's not easy to get SLanguage from LanguageRuntime (which I use to walk extended langs) Set<String> processedLanguages = new HashSet<>(toQualifiedName(initialLanguages)); // set of languages either used (and/or demanded) explicitly in the model we're about to generate, // and languages that may appear during generation process (e.g. by applying some of generators) Set<EngagedLanguage> participatingLanguages = new HashSet<>(queue); while (!queue.isEmpty()) { EngagedLanguage next = queue.remove(); for (LanguageRuntime extendedLang : next.getLanguage().getExtendedLanguages()) { if (processedLanguages.add(extendedLang.getNamespace())) { final EngagedLanguage engaged = new EngagedLanguage(extendedLang, next, "EXTENDS"); participatingLanguages.add(engaged); queue.add(engaged); } } HashSet<EngagedLanguage> targetLanguages = new HashSet<>(); // collect extra languages from generator module description myEngagedTrace.addAll(collectGeneratorsAndTargetLanguages(next, targetLanguages)); for (EngagedLanguage t : targetLanguages) { if (processedLanguages.add(t.getName())) { participatingLanguages.add(t); queue.add(t); } } } // collect unique template models ArrayList<TemplateModule> all = new ArrayList<>(); HashSet<SModuleReference> processedGenerators = new HashSet<>(myEngagedTrace.size() * 2); for (EngagedGenerator m : myEngagedTrace) { final TemplateModule tm = m.getGenerator(); if (processedGenerators.add(tm.getModuleReference())) { all.add(tm); } } return Collections.unmodifiableList(all); } private List<EngagedGenerator> collectGeneratorsAndTargetLanguages(EngagedLanguage lang, Set<EngagedLanguage> targetLanguages) { Collection<? extends GeneratorRuntime> generators = lang.getLanguage().getGenerators(); if (generators == null) { return Collections.emptyList(); } ArrayList<EngagedGenerator> langGenerators = new ArrayList<>(2 + generators.size()); // collect extra languages from generator module description for (GeneratorRuntime gr : generators) { if (false == gr instanceof TemplateModule) { continue; } final TemplateModule generator = (TemplateModule) gr; EngagedGenerator eg = new EngagedGenerator(generator, lang, "OWNED"); langGenerators.add(eg); // handle Used languages targetLanguages.addAll(resolveLanguages(generator.getTargetLanguages(), eg, "GENERATES INTO")); // // handle referenced generators for (TemplateModule tm : generator.getExtendedGenerators()) { langGenerators.add(new EngagedGenerator(tm, eg, "EXTENDED GENERATOR")); } for (TemplateModule tm : generator.getEmployedGenerators()) { langGenerators.add(new EngagedGenerator(tm, eg, "EMPLOYED GENERATOR")); } } return langGenerators; } private Collection<EngagedLanguage> resolveLanguages(Collection<SLanguage> languages, EngagedElement origin, Object engagementKind) { ArrayList<EngagedLanguage> rv = new ArrayList<>(languages.size()); final LinkedHashSet<SLanguage> toResolve = new LinkedHashSet<>(languages); for (SLanguage next : toResolve) { if (myBadLanguages.contains(next)) { // do not resolve more than once continue; } LanguageRuntime language = LanguageRegistry.getInstance().getLanguage(next); if (language == null) { if (origin == null) { final String msg = "Model %s uses language %s which is missing (likely is not yet generated or is a bootstrap dependency)"; LOG.error(String.format(msg, myModel.getName(), next)); } else { String msg = "One of generators engaged for model %s needs ('%s') missing language %s. Please check generator %s"; LOG.error(String.format(msg, myModel.getName(), engagementKind, next, origin.getName())); } myBadLanguages.add(next); } else { rv.add(new EngagedLanguage(language, origin, engagementKind)); } } return rv; } /** * Pumps debug information about engaged generators and the way they got activated as a string. * I know it's better to provide some sort of structured info, but now seems not worth the effort. */ public void dump(Consumer<String> traceConsumer) { myEngagedTrace.forEach(l -> traceConsumer.accept(new StringBuilder().append(' ').append(l).toString())); } // cease existence once we get rid of strings completely private static Collection<String> toQualifiedName(Collection<SLanguage> languages) { return languages.stream().map(SLanguage::getQualifiedName).collect(Collectors.toList()); } private abstract static class EngagedElement { protected final EngagedElement myOrigin; protected final Object myEngagementKind; protected EngagedElement(EngagedElement origin, Object engagementKind) { myOrigin = origin; myEngagementKind = engagementKind; } public abstract String getName(); public EngagedElement getOrigin() { return myOrigin; } @Override public String toString() { String msg = myOrigin == null ? "%s: %s" : "%s: %s as %s through [%s]"; return String.format(msg, getClass().getSimpleName(), getName(), myEngagementKind, myOrigin); } } private static class EngagedLanguage extends EngagedElement { private final LanguageRuntime myLang; EngagedLanguage(@NotNull LanguageRuntime lang, @Nullable EngagedElement origin, @Nullable Object engagementKind) { super(origin, engagementKind); myLang = lang; } public LanguageRuntime getLanguage() { return myLang; } // official lang name aka namespace public String getName() { return myLang.getNamespace(); } } private static class EngagedGenerator extends EngagedElement { private final TemplateModule myGenerator; EngagedGenerator(@NotNull TemplateModule generator, @NotNull EngagedElement origin, @Nullable Object engagementKind) { super(origin, engagementKind); myGenerator = generator; } public TemplateModule getGenerator() { return myGenerator; } public String getName() { return myGenerator.getAlias(); } } }