/* * 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.components.CoreComponent; import jetbrains.mps.extapi.persistence.DataSourceBase; import jetbrains.mps.smodel.SModelId.ModelNameSModelId; import jetbrains.mps.util.IterableUtil; import jetbrains.mps.util.annotation.ToRemove; 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.model.EditableSModel; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SModelId; import org.jetbrains.mps.openapi.model.SModelReference; import org.jetbrains.mps.openapi.module.SModule; import org.jetbrains.mps.openapi.module.SRepository; import org.jetbrains.mps.openapi.module.SRepositoryContentAdapter; import org.jetbrains.mps.openapi.persistence.DataSource; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; // not deprecated yet, despite access and methods are, as it might be reasonable to // keep a facility that gives access to all models of an SRepository (alternative to SRepository.getAllModels method). Or do it with SearchScope? // XXX shall become model-centric view of an SRepository. E.g. would be possible to attach listeners to all models, to keep a snapshot of all models // or to track changes (i.e. that would be too much for a search scope, hence need a separate class). The view, perhaps, could be filtered (e.g. by // Condition<SModel>). Non thread-safe public class SModelRepository implements CoreComponent { private static final Logger LOG = LogManager.getLogger(SModelRepository.class); private final Object myModelsLock = new Object(); private final List<SModel> myAllModels = new ArrayList<SModel>(); private final Map<SModelId, SModel> myIdToModelDescriptorMap = new ConcurrentHashMap<SModelId, SModel>(); /* * SModelRepository used to be global repo listener. With ProjectRepository exposing all modules visible from a project? * however, closing a project (and repository disposal) lead to all models from all modules visible in other projects * to be unregistered from this SModelRepository, and subsequent resolve() of uuid model references (that end up here) * fail, leading to unresolved references. For now, as we still keep single MPSModuleRepository instance, just listen to it * and ignore project repositories, and once we switch to multiple repositories, there would be no SModelRepository. */ private final GlobalRepositoriesListener myRepositoriesListener = new GlobalRepositoriesListener(); private static SModelRepository INSTANCE; private final MPSModuleRepository myRepository; /** * @deprecated global collection of SModels doesn't allow us to move forward. Do not use. */ @Deprecated @ToRemove(version = 3.3) public static SModelRepository getInstance() { return INSTANCE; } public SModelRepository(@NotNull MPSModuleRepository moduleRepository) { myRepository = moduleRepository; } @Override public void init() { if (INSTANCE != null) { throw new IllegalStateException("double initialization"); } INSTANCE = this; new RepoListenerRegistrar(myRepository, myRepositoriesListener).attach(); } @Override public void dispose() { new RepoListenerRegistrar(myRepository, myRepositoriesListener).detach(); INSTANCE = null; } //----------------------------get----------------------------- public List<SModel> getModelDescriptors() { synchronized (myModelsLock) { return new ArrayList<SModel>(myAllModels); } } /** * @deprecated this method makes sense for {@link SModelId#isGloballyUnique() globally unique} model id only, but doesn't manifest this contract. * Use {@link SModelReference#resolve(SRepository)} instead */ @Deprecated @Nullable public SModel getModelDescriptor(@NotNull SModelReference modelReference) { return getModelDescriptor(modelReference.getModelId()); } /** * @deprecated this method makes sense for {@link SModelId#isGloballyUnique() globally unique} model id only, but doesn't manifest this contract. * Use {@link SModelReference#resolve(SRepository)} instead */ @Deprecated public SModel getModelDescriptor(SModelId id) { SModel value = myIdToModelDescriptorMap.get(id); if (value == null && id instanceof ModelNameSModelId) { // inexact search... value = getModelDescriptor(id.getModelName()); } return value; } // XXX there are uses in mbeddr @Deprecated public List<SModel> getModelDescriptorsByModelName(String modelName) { LOG.warn("Use of SModelRepository.getModelDescriptorsByModelName is ineffective, please refactor to use SModelReference"); return getModelDescriptors().stream().filter(m -> modelName.equals(m.getName().getLongName())).collect(Collectors.toList()); } // there's 1 use in mbeddr public List<SModel> getModelDescriptors(SModule module) { return IterableUtil.asList(module.getModels()); } //----------------------------stuff----------------------------- private List<EditableSModel> getModelsToSave() { List<EditableSModel> modelsToSave = new ArrayList<EditableSModel>(); for (SModel md : getModelDescriptors()) { if (!(md instanceof EditableSModel)) continue; EditableSModel emd = ((EditableSModel) md); // HOTFIX MPS-13326 if (emd.isChanged() && !emd.isReadOnly()) { modelsToSave.add(emd); } } return modelsToSave; } /** * Requires write access to model */ public void saveAll() { List<EditableSModel> modelsToRefresh; synchronized (myModelsLock) { modelsToRefresh = getModelsToSave(); } for (EditableSModel emd : modelsToRefresh) { DataSource source = emd.getSource(); if (source instanceof DataSourceBase) { ((DataSourceBase) source).refresh(); } } synchronized (myModelsLock) { for (EditableSModel emd : getModelsToSave()) { try { emd.save(); } catch (Throwable t) { LOG.error(t); } } } } //---------------------------events---------------------------- public void addModelRepositoryListener(@NotNull SModelRepositoryListener l) { throw new UnsupportedOperationException("SModelRepositoryListener has been deprecated since MPS 3.2, use openapi change notification mechanism instead."); } public void removeModelRepositoryListener(@NotNull SModelRepositoryListener l) { throw new UnsupportedOperationException("SModelRepositoryListener has been deprecated since MPS 3.2, use openapi change notification mechanism instead."); } // FIXME Why this method is different in implementation from #getModelDescriptorsByModelName(String modelName)? // This one takes full name, including stereotype, while getModelDescriptorsByModelName() cares about fqn only public SModel getModelDescriptor(String modelName) { if (modelName == null) { return null; } LOG.warn("Use of SModelRepository.getModelDescriptor(String) is ineffective, please refactor to use SModelReference"); return getModelDescriptors().stream().filter(m -> m.getName().getValue().equals(modelName)).findFirst().orElse(null); } private class GlobalRepositoriesListener extends SRepositoryContentAdapter { @Override protected void startListening(SModel model) { SModelId modelId = model.getModelId(); if (modelId.isGloballyUnique()) { myIdToModelDescriptorMap.put(modelId, model); } synchronized (myModelsLock) { myAllModels.add(model); } } @Override protected void stopListening(SModel model) { synchronized (myModelsLock) { myAllModels.remove(model); } myIdToModelDescriptorMap.remove(model.getModelId()); } } }