/* * 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.text.impl; import jetbrains.mps.components.CoreComponent; import jetbrains.mps.smodel.ModelDependencyScanner; import jetbrains.mps.smodel.SNodeUtil; import jetbrains.mps.smodel.language.LanguageRegistry; import jetbrains.mps.smodel.language.LanguageRegistryListener; import jetbrains.mps.smodel.language.LanguageRuntime; import jetbrains.mps.text.MissingTextGenDescriptor; import jetbrains.mps.text.rt.TextGenAspectDescriptor; import jetbrains.mps.text.rt.TextGenDescriptor; import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; 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.util.ImmediateParentConceptIterator; import java.util.ArrayList; import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * Excerpt from ConceptRegistry related to TextGenDescriptor. * It's artifact of refactoring to break [textgen] and [kernel] cycle dependency. * FIXME For the time being, it's initialized together with ConceptRegistry from MPSCore, though shall be separate ComponentPlugin, * like MPSGenerator, and initialized from MPSCoreComponents and alike. * @author Artem Tikhomirov * @since 3.3 */ public class TextGenRegistry implements CoreComponent, LanguageRegistryListener { private static TextGenRegistry INSTANCE; private final Map<String, TextGenDescriptor> textGenDescriptors = new ConcurrentHashMap<String, TextGenDescriptor>(); private final LanguageRegistry myLanguageRegistry; /*package*/ TextGenRegistry(@NotNull LanguageRegistry languageRegistry) { myLanguageRegistry = languageRegistry; } @Override public void init() { if (INSTANCE != null) { throw new IllegalStateException("double initialization"); } INSTANCE = this; myLanguageRegistry.addRegistryListener(this); } @Override public void dispose() { myLanguageRegistry.removeRegistryListener(this); INSTANCE = null; } public static TextGenRegistry getInstance() { return INSTANCE; } /** * @param node * @return <code>true</code> if there's a TextGen for the node */ public boolean hasTextGen(@NotNull SNode node) { return !(getTextGenDescriptor(node) instanceof MissingTextGenDescriptor); } @NotNull public TextGenDescriptor getTextGenDescriptor(@Nullable SNode node) { if (node == null) { // FIXME default implementation doesn't expect null node return new MissingTextGenDescriptor(); } return getTextGenDescriptor(node.getConcept()); } private TextGenDescriptor getTextGenDescriptor(SConcept concept) { final String fqName = concept.getQualifiedName(); TextGenDescriptor descriptor = textGenDescriptors.get(fqName); if (descriptor != null) { return descriptor; } // Would be nice if TGAD could answer for any subtype from the same language, i.e. when there's TextGen for A, // and there's B extends A, and we ask for B's textgen, TGAD might answer with A's right away. Then, we could // ask each language only once // TODO HashSet<SLanguage> seen = new HashSet<SLanguage>(); for (SConcept next : new ImmediateParentConceptIterator(concept, SNodeUtil.concept_BaseConcept)) { TextGenAspectDescriptor textGenAspectDescriptor = getAspect(next); if (textGenAspectDescriptor == null) { continue; } descriptor = textGenAspectDescriptor.getDescriptor(next); if (descriptor != null) { break; } } if (descriptor == null) { descriptor = new MissingTextGenDescriptor(); } textGenDescriptors.put(fqName, descriptor); return descriptor; } @Nullable private TextGenAspectDescriptor getAspect(SConcept concept) { LanguageRuntime languageRuntime = myLanguageRegistry.getLanguage(concept.getLanguage()); if (languageRuntime == null) { // Then language was just renamed and was not re-generated then it can happen that it has no Logger.getLogger(TextGenRegistry.class).warn(String.format("No language for concept %s, while looking for textgen descriptor.", concept)); return null; } else { return languageRuntime.getAspect(TextGenAspectDescriptor.class); } } /** * @param model model to generate text from * @return aspect runtime instances for all languages involved */ @NotNull public Collection<TextGenAspectDescriptor> getAspects(@NotNull SModel model) { // FIXME likely, shall collect all extended languages as well, as there might be instances of a language without textgen in the model, // while textgen elements are derived from extended language. HOWEVER, need to process breakdownToTextUnits carefully, so that default // file-per-root breakdown doesn't create duplicates! ArrayList<TextGenAspectDescriptor> rv = new ArrayList<TextGenAspectDescriptor>(5); final ModelDependencyScanner modelScanner = new ModelDependencyScanner(); modelScanner.crossModelReferences(false).usedLanguages(true).walk(model); for (SLanguage l : modelScanner.getUsedLanguages()) { final LanguageRuntime lr = myLanguageRegistry.getLanguage(l); if (lr == null) { // XXX shall report missing language? continue; } final TextGenAspectDescriptor rtAspect = lr.getAspect(TextGenAspectDescriptor.class); if (rtAspect != null) { rv.add(rtAspect); } } return rv; } @Override public void beforeLanguagesUnloaded(Iterable<LanguageRuntime> languages) { // @see ConceptRegistry#beforeLanguagesUnloaded } @Override public void afterLanguagesLoaded(Iterable<LanguageRuntime> languages) { textGenDescriptors.clear(); } }