/* * 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.smodel; import jetbrains.mps.components.CoreComponent; import jetbrains.mps.extapi.module.SRepositoryRegistry; import jetbrains.mps.extapi.persistence.FileDataSource; import jetbrains.mps.extapi.persistence.FolderDataSource; import jetbrains.mps.util.Computable; import jetbrains.mps.util.ModelComputeRunnable; import jetbrains.mps.vfs.IFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.model.SModel; 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.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; /** * Bridge VFS and Model worlds in MPS. * Capable to tell {@link SModel} for a {@link IFile} * Models are tied to repository, same file may get loaded into few distinct repositories, that's why * instances of the tracker are per-repository. * * IMPLEMENTATION: now it tracks all model files and {@link #findModel(IFile) answers} quickly. Do we care to do it really fast at expense of huge * (there are thousands of models in a project) memory footprint? FIXME Shall measure footprint first, then re-consider * * XXX CoreComponent: what if we introduce notion of 'services' for an SRepository, so that there's no need to use static accessor (getInstance(SRepo))? * i.e. instead we'd have smth like SRepo.getService(SModelFileTracker.class) */ public class SModelFileTracker { private static final List<SModelFileTracker> ourModelTrackers = new CopyOnWriteArrayList<SModelFileTracker>(); private final RepositoryModelTracker myListener = new RepositoryModelTracker(); private final SRepository myRepository; private SModelFileTracker(SRepository repository) { myRepository = repository; } @NotNull public static SModelFileTracker getInstance(SRepository repository) { SModelFileTracker t = findInstance(repository); if (t != null) { return t; } synchronized (ourModelTrackers) { // despite ourModelTrackers is COW list, we don't want two parallel threads to add two identical SModelFileTracker instances, // hence we lock for modifications and check again prior to adding a new one. t = findInstance(repository); if (t != null) { return t; } ourModelTrackers.add(t = new SModelFileTracker(repository)); } t.attach(); return t; } private static SModelFileTracker findInstance(SRepository repository) { for (SModelFileTracker t : ourModelTrackers) { if (t.myRepository == repository) { return t; } } return null; } /*package*/ void attach() { new RepoListenerRegistrar(myRepository, myListener).attach(); } /*package*/ void detach() { new RepoListenerRegistrar(myRepository, myListener).detach(); // FIXME At the moment, there's no notification mechanism to find out about repository gone. } @Nullable public SModel findModel(@Nullable IFile modelFile) { if (modelFile == null) { return null; } SModelReference mr = myListener.findModel(modelFile); if (mr == null) { return null; } // FIXME shall I extract this class, similar to RepoListenerRegistrar? Or shall I demand model read provided clients are gonna read model anyway? class ModelResolve implements Computable<SModel> { private final SModelReference myModelReference; private final SRepository myRepository; public ModelResolve(SModelReference modelReference, SRepository repository) { myModelReference = modelReference; myRepository = repository; } @Override public SModel compute() { return myModelReference.resolve(myRepository); } public SModel resolve() { if (myRepository.getModelAccess().canRead()) { return compute(); } else { return new ModelComputeRunnable<SModel>(this).runRead(myRepository.getModelAccess()); } } } return new ModelResolve(mr, myRepository).resolve(); } public static class Plug implements CoreComponent { public Plug(SRepositoryRegistry repositoryRegistry) { // FIXME may listen to repositories come and go, instantiate (and dispose!) trackers as appropriate } @Override public void init() { } @Override public void dispose() { ourModelTrackers.clear(); } } private static class RepositoryModelTracker extends SRepositoryContentAdapter { private final Map<IFile, SModelReference> myPathsToModels = new HashMap<IFile, SModelReference>(256); @Override protected void startListening(SModel model) { addModelToFileCache(model); } @Override protected void stopListening(SModel model) { removeModelFromFileCache(model); } @Override public void beforeModelRenamed(SModule module, SModel model, SModelReference newRef) { removeModelFromFileCache(model); } @Override public void modelRenamed(SModule module, org.jetbrains.mps.openapi.model.SModel model, SModelReference oldRef) { addModelToFileCache(model); } private void addModelToFileCache(SModel md) { DataSource source = md.getSource(); if (!(source instanceof FileDataSource || source instanceof FolderDataSource)) { return; } IFile file = source instanceof FileDataSource ? ((FileDataSource) source).getFile() : ((FolderDataSource) source).getFolder(); myPathsToModels.put(file, md.getReference()); } private void removeModelFromFileCache(SModel md) { DataSource source = md.getSource(); if (!(source instanceof FileDataSource || source instanceof FolderDataSource)) return; IFile file = source instanceof FileDataSource ? ((FileDataSource) source).getFile() : ((FolderDataSource) source).getFolder(); myPathsToModels.remove(file); } /*package*/ SModelReference findModel(IFile modelFile) { return myPathsToModels.get(modelFile); } } }