/* * 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.references; import jetbrains.mps.components.CoreComponent; import jetbrains.mps.project.ModuleId; import jetbrains.mps.project.structure.modules.ModuleReference; import jetbrains.mps.smodel.RepoListenerRegistrar; import jetbrains.mps.smodel.SModelId; import jetbrains.mps.smodel.SReferenceBase; 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.PersistenceFacade; import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; public class ImmatureReferences implements CoreComponent { // How many threads _simultaneously_ accessing the pool are allowed to succeed without congestion private static final int POOL_SIZE = 4; private static final Object PRESENT = new Object(); private static ImmatureReferences INSTANCE; private final SModelReference myVirtualRef; // FIXME shall retrieve instance per SRepository public static ImmatureReferences getInstance() { return INSTANCE; } // seems sufficient to keep immature references per SRepository (unlike SModelRepository, which used to track models from all repositories) // however, shall fix getInstance to respect actual repository private final SRepository myRepository; private final SRepositoryContentAdapter myReposListener = new MyRepositoryAdapter(); private final ConcurrentMap<SModelReference, ConcurrentMap<SReferenceBase, Object>> myReferences = new ConcurrentHashMap<>(); private ConcurrentLinkedQueue<ConcurrentMap<SReferenceBase, Object>> myReferencesSetPool = new ConcurrentLinkedQueue<>(); private boolean myDisabled = true; public ImmatureReferences(SRepository repository, PersistenceFacade persistenceFacade) { myRepository = repository; for (int i = 0; i < POOL_SIZE; i++) { myReferencesSetPool.add(new ConcurrentHashMap<>()); } myVirtualRef = persistenceFacade.createModelReference(new ModuleReference("$ImmatureRefsModuleRef$", ModuleId.regular()), SModelId.generate(), "$ImmatureRefsModelRef$"); } public void enable() { myDisabled = false; } public void disable() { myDisabled = true; cleanup(); } @Override public void init() { if (INSTANCE != null) { throw new IllegalStateException("double initialization"); } INSTANCE = this; new RepoListenerRegistrar(myRepository, myReposListener).attach(); } @Override public void dispose() { new RepoListenerRegistrar(myRepository, myReposListener).detach(); INSTANCE = null; } public void cleanup() { for (Entry<SModelReference, ConcurrentMap<SReferenceBase, Object>> entry : myReferences.entrySet()) { for (SReferenceBase r : entry.getValue().keySet()) { r.makeIndirect(true); } } myReferences.clear(); } public void add(SReferenceBase ref) { if (myDisabled) return; SModel model = ref.getSourceNode().getModel(); SModelReference modelRef = model == null ? myVirtualRef : model.getReference(); ConcurrentMap<SReferenceBase, Object> refSet = getOrCreateRefSet(modelRef); refSet.put(ref, PRESENT); } public void remove(SReferenceBase ref) { if (myDisabled) return; SModel model = ref.getSourceNode().getModel(); SModelReference modelRef = model == null ? myVirtualRef : model.getReference(); ConcurrentMap<SReferenceBase, Object> refSet = myReferences.get(modelRef); if (refSet != null) { refSet.remove(ref); } } private ConcurrentMap<SReferenceBase, Object> getOrCreateRefSet(SModelReference modelRef) { ConcurrentMap<SReferenceBase, Object> pooledSet; try { pooledSet = myReferencesSetPool.remove(); } catch (NoSuchElementException e) { pooledSet = new ConcurrentHashMap<>(); } ConcurrentMap<SReferenceBase, Object> usedSet = myReferences.putIfAbsent(modelRef, pooledSet); if (usedSet == null) { usedSet = pooledSet; pooledSet = new ConcurrentHashMap<>(); } myReferencesSetPool.add(pooledSet); return usedSet; } private class MyRepositoryAdapter extends SRepositoryContentAdapter { @Override public void beforeModelRemoved(SModule module, SModel model) { super.beforeModelRemoved(module, model); ConcurrentMap<SReferenceBase, Object> refSet = myReferences.remove(model.getReference()); if (refSet != null) { refSet.clear(); } } } }