/* * 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.smodel; import jetbrains.mps.classloading.ModuleClassLoaderSupport; import jetbrains.mps.classloading.ModuleIsNotLoadableException; import jetbrains.mps.extapi.module.SRepositoryExt; import jetbrains.mps.library.ModulesMiner; import jetbrains.mps.module.ReloadableModule; import jetbrains.mps.module.ReloadableModuleBase; import jetbrains.mps.module.SDependencyImpl; import jetbrains.mps.project.DescriptorTargetFileAlreadyExistsException; import jetbrains.mps.project.ModelsAutoImportsManager; import jetbrains.mps.project.facets.JavaModuleFacet; import jetbrains.mps.project.facets.JavaModuleOperations; import jetbrains.mps.project.facets.TestsFacet; import jetbrains.mps.project.persistence.LanguageDescriptorPersistence; import jetbrains.mps.project.structure.modules.GeneratorDescriptor; import jetbrains.mps.project.structure.modules.LanguageDescriptor; import jetbrains.mps.project.structure.modules.ModuleDescriptor; import jetbrains.mps.reloading.ClassBytesProvider.ClassBytes; import jetbrains.mps.reloading.IClassPathItem; import jetbrains.mps.smodel.language.LanguageAspectSupport; import jetbrains.mps.util.IterableUtil; import jetbrains.mps.util.MacrosFactory; import jetbrains.mps.util.NameUtil; import jetbrains.mps.util.ProtectionDomainUtil; import jetbrains.mps.util.annotation.ToRemove; import jetbrains.mps.vfs.IFile; 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.model.SModelReference; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.module.SDependency; import org.jetbrains.mps.openapi.module.SDependencyScope; import org.jetbrains.mps.openapi.module.SModule; import org.jetbrains.mps.openapi.module.SModuleReference; import org.jetbrains.mps.openapi.module.SRepository; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Objects; import java.util.Set; public class Language extends ReloadableModuleBase implements MPSModuleOwner, ReloadableModule { private static final Logger LOG = LogManager.getLogger(Language.class); /** * Default, although not mandatory location we save our models to. * Made public just for the sake of tests. */ public static final String LANGUAGE_MODELS = "models"; /** * @deprecated Use of default value to detect aspect source root or to check module existence is wrong. */ @Deprecated @ToRemove(version = 3.3) public static final String LEGACY_LANGUAGE_MODELS = "languageModels"; static { ModelsAutoImportsManager.registerContributor(new LanguageModelsAutoImports()); } @NotNull private LanguageDescriptor myLanguageDescriptor; private ClassLoader myStubsLoader = new StubsClassLoader(); protected Language(@NotNull LanguageDescriptor descriptor, @Nullable IFile file) { super(file); myLanguageDescriptor = descriptor; setModuleReference(descriptor.getModuleReference()); } @Override public void reloadAfterDescriptorChange() { super.reloadAfterDescriptorChange(); revalidateGenerators(); } public void addExtendedLanguage(@NotNull SModuleReference langRef) { if (myLanguageDescriptor.getExtendedLanguages().contains(langRef)) return; LanguageDescriptor moduleDescriptor = getModuleDescriptor(); moduleDescriptor.getExtendedLanguages().add(langRef); dependenciesChanged(); setChanged(); fireChanged(); } public Set<SModuleReference> getExtendedLanguageRefs() { HashSet<SModuleReference> res = new HashSet<SModuleReference>(myLanguageDescriptor.getExtendedLanguages()); if (!BootstrapLanguages.coreLanguageRef().equals(getModuleReference())) { //this is needed now as we don't force the user to have an explicit dependency on core res.add(BootstrapLanguages.coreLanguageRef()); } return res; } @Override public Iterable<SDependency> getDeclaredDependencies() { HashSet<SDependency> rv = new HashSet<SDependency>(IterableUtil.asCollection(super.getDeclaredDependencies())); final SRepository repo = getRepository(); for (SModuleReference language : getExtendedLanguageRefs()) { // XXX not clear whether it's worth including implicit "extends lang.core" (see getExtendedLanguageRefs()) // or adhere to 'declared' in the name of getDeclaredDependencies and use myLanguageDescriptor.getExtendedLanguages() only rv.add(new SDependencyImpl(language, repo, SDependencyScope.EXTENDS, true)); } return rv; } /** * All the language modules extended by this one within the same repository this module is attached to. * For detached module, the set returned is empty. To access 'raw' information about extended languages, * one could use {@link #getExtendedLanguageRefs()}. * * This method requires model read access as it resolves modules. * * IMPORTANT: if any extended language is missing from the repository of the module, it's simply ignored and not included into outcome * (nor the closure of its extended languages). * * NOTE, implementation hides cyclic dependencies between languages, e.g if "A extends B extends A", * you'd get "A extends B" for A and "B extends A" for B. */ @NotNull public Set<Language> getAllExtendedLanguages() { HashSet<Language> langs = new HashSet<Language>(); final SRepository repository = getRepository(); if (repository == null) { return langs; } ArrayDeque<Language> queue = new ArrayDeque<Language>(); queue.add(this); do { Language current = queue.poll(); if (!langs.add(current)) { continue; } for (SModuleReference lr : current.getExtendedLanguageRefs()) { final SModule l = lr.resolve(repository); if (l instanceof Language) { queue.add((Language) l); } } } while (!queue.isEmpty()); return langs; } public Collection<SModuleReference> getRuntimeModulesReferences() { return Collections.unmodifiableSet(myLanguageDescriptor.getRuntimeModules()); } @Override protected ModuleDescriptor loadDescriptor() { return new ModulesMiner().loadModuleHandle(getDescriptorFile()).getDescriptor(); } public void validateExtends() { List<SModuleReference> remove = new ArrayList<SModuleReference>(); for (SModuleReference ref : myLanguageDescriptor.getExtendedLanguages()) { if (getModuleName().equals(ref.getModuleName())) { remove.add(ref); } } if (!remove.isEmpty()) { myLanguageDescriptor.getExtendedLanguages().removeAll(remove); setChanged(); } } @Override public void onModuleLoad() { super.onModuleLoad(); validateExtends(); } /* * Update repository generator modules associated with this language with descriptors known to the language (registers new generators, if necessary) */ private void revalidateGenerators() { LinkedList<Generator> existingGenerators = new LinkedList<>(getGenerators()); SRepositoryExt moduleRepository = (SRepositoryExt) getRepository(); for (GeneratorDescriptor nextDescriptor : myLanguageDescriptor.getGenerators()) { Generator nextGenerator = null; for (Iterator<Generator> it = existingGenerators.iterator(); it.hasNext(); ) { // looking for the existing generator with same ID Generator nextGeneratorCandidate = it.next(); GeneratorDescriptor nextGeneratorCandidateDescriptor = nextGeneratorCandidate.getModuleDescriptor(); if (Objects.equals(nextGeneratorCandidateDescriptor.getNamespace(), nextDescriptor.getNamespace()) && Objects.equals(nextGeneratorCandidateDescriptor.getId(), nextDescriptor.getId())) { nextGenerator = nextGeneratorCandidate; it.remove(); break; } } if (nextGenerator != null) { nextGenerator.updateGeneratorDescriptor(nextDescriptor); } else { Generator generator = new Generator(this, nextDescriptor); moduleRepository.registerModule(generator, this); } } for (Generator stale : existingGenerators) { moduleRepository.unregisterModule(stale, this); } } @Override public void dispose() { new ModuleRepositoryFacade(getRepository()).unregisterModules(this); super.dispose(); } @NotNull @Override public LanguageDescriptor getModuleDescriptor() { return myLanguageDescriptor; } @Override public void doSetModuleDescriptor(ModuleDescriptor moduleDescriptor) { assert moduleDescriptor instanceof LanguageDescriptor; myLanguageDescriptor = (LanguageDescriptor) moduleDescriptor; SModuleReference reference = new jetbrains.mps.project.structure.modules.ModuleReference(myLanguageDescriptor.getNamespace(), myLanguageDescriptor.getId()); setModuleReference(reference); if (getRepository() instanceof MPSModuleRepository) { ((MPSModuleRepository) getRepository()).invalidateCaches(); } // XXX in fact, getRepository could be ProjectRepository that delegates to MPSModuleRepository, and to clean at least this module's scope, // do it here explicitly. ((ModuleScope) getScope()).invalidateCaches(); } // fixme: remove, use #setModuleDescriptor instead @Deprecated public void setLanguageDescriptor(@NotNull final LanguageDescriptor moduleDescriptor) { setModuleDescriptor(moduleDescriptor); } public int getLanguageVersion() { return getModuleDescriptor().getLanguageVersion(); } public void setLanguageVersion(int version) { getModuleDescriptor().setLanguageVersion(version); fireChanged(); setChanged(); } public Collection<Generator> getGenerators() { SRepository repo = getRepository(); if (repo == null) { return Collections.emptyList(); } // Language module doesn't track Generator modules it is owner to. Instead, it relies on a repository // to know actual set of generators. I expect Language to cease being module owner once Generators are full-fledged stand-alone // modules and get into repository without help of a language module. // OTOH, I don't have strong objection against a pattern when a subordinate registers with its master, and master keeps track of // subordinates (e.g. new Generator(Language source) might tell source.iAmYourServant(this), which would keep collection of Generators // so that we don't need to go into repository). It just feels more flexible when the two are not bound too tightly (with expense of repository access). return new ModuleRepositoryFacade(repo).getModules(this, Generator.class); } @Override public void rename(@NotNull String newNamespace) throws DescriptorTargetFileAlreadyExistsException { for (Generator g : getGenerators()) { g.rename(newNamespace); } super.rename(newNamespace); } /** * @deprecated method is not bad per se (Language module could tell SNode with concept declaration. However, * it silently excludes Interface concepts, and likely its uses need attention and switch to SConcept. * Then, we could decide whether we truly need access to language's concept nodes this way, or shall use * LanguageAspects instead. */ @Deprecated @ToRemove(version = 3.4) public List<SNode> getConceptDeclarations() { // FIXME thera are uses in mbeddr SModel structureModel = getStructureModelDescriptor(); if (structureModel == null) return Collections.emptyList(); return FastNodeFinderManager.get(structureModel).getNodes(SNodeUtil.concept_ConceptDeclaration, true); } public List<SModel> getUtilModels() { Set<SModel> models = new HashSet<SModel>(getModels()); models.removeAll(LanguageAspectSupport.getAspectModels(this)); models.removeAll(getAccessoryModels()); List<SModel> result = new ArrayList<SModel>(models.size()); for (SModel md : models) { String st = SModelStereotype.getStereotype(md); if (SModelStereotype.isStubModelStereotype(st) || SModelStereotype.isDescriptorModelStereotype(st)) { // perhaps, we need more generic isPredefinedStereotypeMPS() continue; } result.add((md)); } return result; } public SModel getStructureModelDescriptor() { return LanguageAspect.STRUCTURE.get(this); } /** * fixme why generator saves language?? * generator is contained in language it must be the other way around! */ @Override public void save() { super.save(); if (isReadOnly()) return; if (myLanguageDescriptor.getLoadException() != null){ return; } LanguageDescriptorPersistence.saveLanguageDescriptor(myDescriptorFile, getModuleDescriptor(), MacrosFactory.forModuleFile(myDescriptorFile)); } public List<SModel> getAccessoryModels() { List<SModel> result = new LinkedList<>(); for (SModelReference model : getModuleDescriptor().getAccessoryModels()) { SModel modelDescriptor = model.resolve(getRepository()); if (modelDescriptor != null) { result.add(modelDescriptor); } } return result; } public boolean isAccessoryModel(org.jetbrains.mps.openapi.model.SModelReference modelReference) { return myLanguageDescriptor.getAccessoryModels().stream().anyMatch(m -> Objects.equals(m, modelReference)); } public void removeAccessoryModel(org.jetbrains.mps.openapi.model.SModel sm) { // XXX why removal of accessory model is not done through ModuleDescriptor as other editing activities? final SModelReference accessoryModelRef = sm.getReference(); boolean changed = myLanguageDescriptor.getAccessoryModels().removeIf(m -> accessoryModelRef.equals(m)); if (changed) { setModuleDescriptor(myLanguageDescriptor); reload(); } } public String toString() { return getModuleName() + " [language]"; } @Deprecated @ToRemove(version = 3.3) //no full equivalent to this method, use appropriate method from LanguageAspectSupport public LanguageAspect getAspectForModel(@NotNull org.jetbrains.mps.openapi.model.SModel sm) { for (LanguageAspect la : LanguageAspect.values()) { if (la.get(this) == sm) { return la; } } return null; } public static Language getLanguageForLanguageAspect(org.jetbrains.mps.openapi.model.SModel modelDescriptor) { return getLanguageFor(modelDescriptor); } @Deprecated @ToRemove(version = 3.3) //no full equivalent to this method, use appropriate method from LanguageAspectSupport //no usages in MPS @Nullable public static LanguageAspect getModelAspect(org.jetbrains.mps.openapi.model.SModel sm) { if (sm == null) return null; SModule module = sm.getModule(); if (!(module instanceof Language)) { return null; } Language l = (Language) module; return l.getAspectForModel(sm); } public static boolean isLanguageOwnedAccessoryModel(org.jetbrains.mps.openapi.model.SModel sm) { SModule modelOwner = sm.getModule(); if (modelOwner instanceof Language) { Language l = (Language) modelOwner; if (l.isAccessoryModel(sm.getReference())) { return true; } } return false; } public static Language getLanguageFor(org.jetbrains.mps.openapi.model.SModel sm) { SModule owner = sm.getModule(); if (owner instanceof Language) { return (Language) owner; } return null; } @Override protected void collectMandatoryFacetTypes(Set<String> types) { super.collectMandatoryFacetTypes(types); types.add(TestsFacet.FACET_TYPE); } // TODO // @Nullable // @Override // public Language clone(String targetRoot, String targetNamespace) { // LanguageDescriptor targetDescriptor = new LanguageDescriptor(); // IFile targetDescriptorFile = getFileSystem().getFile(targetRoot + File.separator + targetNamespace + MPSExtentions.DOT_LANGUAGE); // // targetDescriptor.setId(ModuleId.regular()); // targetDescriptor.setNamespace(targetNamespace); // getModuleDescriptor().cloneTo(targetDescriptor, PathConverters.forDescriptorFiles(targetDescriptorFile, getDescriptorFile())); // LanguageDescriptorPersistence.saveLanguageDescriptor(targetDescriptorFile, targetDescriptor, MacrosFactory.forModuleFile(targetDescriptorFile)); // // Language targetLanguage = new Language(targetDescriptor, targetDescriptorFile); // ModelRootCloneUtil.cloneModelRootsTo(getModelRoots(), targetLanguage); // // Iterator<Generator> targetGenerators = targetLanguage.getGenerators().iterator(); // for (Generator generator : getGenerators()) { // Generator targetGenerator = targetGenerators.next(); // ModelRootCloneUtil.cloneModelRootsTo(generator.getModelRoots(), targetGenerator); // } // // FIXME RADIMIR rename models here // // return targetLanguage; // } @Override public boolean isHidden() { return false; } @NotNull protected Class<?> getClass(String classFqName, boolean ownClassOnly) throws ClassNotFoundException, ModuleIsNotLoadableException { // first check if class comes from stubs if (classFqName.startsWith(getModuleName() + ".stubs.")) { try { return myStubsLoader.loadClass(classFqName); } catch (ClassNotFoundException e) { LOG.error("Exception during stubs' class loading", e); throw e; } } // if not then call standard #getClass return super.getClass(classFqName, ownClassOnly); } private class StubsClassLoader extends ClassLoader { public StubsClassLoader() { super(Language.class.getClassLoader()); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { JavaModuleFacet facet = Language.this.getFacet(JavaModuleFacet.class); assert facet != null; IClassPathItem classPathItem = JavaModuleOperations.createClassPathItem(facet.getClassPath(), ModuleClassLoaderSupport.class.getName()); ClassBytes classBytes = classPathItem.getClassBytes(name); if (classBytes == null) return null; byte[] bytes = classBytes.getBytes(); definePackageIfNecessary(name); return defineClass(name, bytes, 0, bytes.length, ProtectionDomainUtil.loadedClassDomain(classBytes.getPath())); } private void definePackageIfNecessary(String name) { String pack = NameUtil.namespaceFromLongName(name); if (getPackage(pack) != null) return; definePackage(pack, null, null, null, null, null, null, null); } } private static class LanguageModelsAutoImports extends jetbrains.mps.project.ModelsAutoImportsManager.AutoImportsContributor<Language> { @NotNull @Override public Class<Language> getApplicableSModuleClass() { return Language.class; } @NotNull @Override public Collection<SLanguage> getLanguages(Language contextModule, SModel model) { return LanguageAspectSupport.getMainLanguages(model); } @Override public Collection<SModuleReference> getDevKits(Language contextModule, SModel forModel) { return Collections.singleton(BootstrapLanguages.getLanguageDesignDevKit()); } } }