/* * 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.project.structure; import jetbrains.mps.components.CoreComponent; import jetbrains.mps.smodel.SModelStereotype; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SModelListener; import org.jetbrains.mps.openapi.model.SModelListenerBase; import org.jetbrains.mps.openapi.model.SModelReference; import org.jetbrains.mps.openapi.module.SModule; import org.jetbrains.mps.openapi.module.SModuleListenerBase; import org.jetbrains.mps.openapi.module.SRepository; import org.jetbrains.mps.openapi.module.SRepositoryAttachListener; import org.jetbrains.mps.openapi.module.SRepositoryListenerBase; /** * Contribute descriptor models to modules of a given repository. * For now, global {@link CoreComponent}, although shall become per-project/per-repository component. * OOH, it seems to be project-related, as it generally cares to contribute models to workbench modules only, OTOH we * don't have project always (e.g. when building/generating modules from Ant), but we still need these models in that scenario as well. * Either shall introduce Project everywhere, and dedicated layer of ProjectCoreComponents, or need a better solution to address lifecycle of * repository-specific components (there are others, e.g. FastNodeFinderManager) * @author Artem Tikhomirov * @since 3.4 */ public class DescriptorModelComponent implements CoreComponent { private final SRepository myRepository; private final ModuleTracker myListener; public DescriptorModelComponent(SRepository repository, DescriptorModelProvider... providers) { myRepository = repository; myListener = new ModuleTracker(providers); } @Override public void init() { myRepository.getModelAccess().runWriteAction(() -> myRepository.addRepositoryListener(myListener)); } @Override public void dispose() { // it's vital to have exclusive access to a repository as we contribute/revoke models myRepository.getModelAccess().runWriteAction(() -> myRepository.removeRepositoryListener(myListener)); myListener.disposeProviders(); } private static class ModuleTracker extends SRepositoryListenerBase implements SRepositoryAttachListener { private final DescriptorModelProvider[] myProviders; // listener is attached to modules of interest only private final SModuleListenerBase myModuleListener = new SModuleListenerBase() { @Override public void modelAdded(SModule module, SModel model) { if (SModelStereotype.isDescriptorModel(model)) { // it's me who have added the model, no need to refresh return; } refresh(module); attachListeners(model); } @Override public void beforeModelRemoved(SModule module, SModel model) { detachListeners(model); } @Override public void modelRemoved(SModule module, SModelReference ref) { if (SModelStereotype.isDescriptorModelStereotype(ref.getName().getStereotype())) { return; // the only source of descriptor models is our provider, no need to refresh } // shall refresh once model is gone, not when it's yet about to be removed refresh(module); } @Override public void moduleChanged(SModule module) { refresh(module); } }; // listener is attached to models in modules of interest only private final SModelListener myModelListener = new SModelListenerBase() { // FIXME would benefit of generic ModelChanged event here, to avoid tracking each distinct node change @Override public void modelReplaced(SModel model) { SModule module = model.getModule(); if (module != null) { refresh(module); } } @Override public void modelSaved(SModel model) { SModule module = model.getModule(); if (module != null) { refresh(module); } } }; ModuleTracker(DescriptorModelProvider[] providers) { myProviders = providers; } /** * @param module not null * @return <code>true</code> if there's a provider interested in the module */ boolean refresh(SModule module) { boolean rv = false; for (DescriptorModelProvider mp : myProviders) { if (mp.isApplicable(module)) { mp.refreshModule(module); rv = true; } } return rv; } /** * @param module not null * @return <code>true</code> if there's a provider interested in the module */ boolean forget(SModule module) { boolean rv = false; for (DescriptorModelProvider mp : myProviders) { if (mp.isApplicable(module)) { mp.forgetModule(module); rv = true; } } return rv; } @Override public void startListening(@NotNull SRepository repository) { for (SModule module : repository.getModules()) { if (refresh(module)) { attachListeners(module); } } } @Override public void stopListening(@NotNull SRepository repository) { for (SModule module : repository.getModules()) { if (forget(module)) { detachListeners(module); } } } @Override public void moduleAdded(@NotNull SModule module) { if (refresh(module)) { attachListeners(module); } } @Override public void beforeModuleRemoved(@NotNull SModule module) { // in fact, shall detach listeners before actual forget, as removal of the descriptor model // triggers model events we do not really care about. Shall rather keep set of SModuleReferences we've attached listeners to. if (forget(module)) { detachListeners(module); } } // module is of interest to one of providers private void attachListeners(SModule module) { // FIXME would benefit from SModuleAttachListener, like SRepositoryAttachListener, to get the code to attach to each model in a single place for (SModel m : module.getModels()) { attachListeners(m); } module.addModuleListener(myModuleListener); } private void detachListeners(SModule module) { for (SModel m : module.getModels()) { detachListeners(m); } module.removeModuleListener(myModuleListener); } // model comes from a module of interest to one of providers void attachListeners(SModel model) { if (!SModelStereotype.isDescriptorModel(model)) { model.addModelListener(myModelListener); } } void detachListeners(SModel model) { if (!SModelStereotype.isDescriptorModel(model)) { model.removeModelListener(myModelListener); } } /*package*/ void disposeProviders() { for (DescriptorModelProvider mp : myProviders) { mp.dispose(); } } } }