/*
* Copyright 2003-2014 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.smodel.impl.StructureAspectChangeTracker;
import jetbrains.mps.smodel.impl.StructureAspectChangeTracker.ModuleListener;
import org.jetbrains.annotations.NotNull;
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.SModuleReference;
import org.jetbrains.mps.openapi.module.SRepository;
import org.jetbrains.mps.openapi.module.SRepositoryContentAdapter;
import java.util.ArrayList;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Entry point to access {@link jetbrains.mps.smodel.FastNodeFinder} instance for a class.
*
* Likely to become {@link jetbrains.mps.components.CoreComponent}, then static fields would become instance fields
* and it would be caller's responsibility to obtain instance of this class.
*
* XXX lives in kernel as it depends now from classes that require kernel classes. Consider refactoring and relocating to smodel module.
* @author Artem Tikhomirov
*/
public class FastNodeFinderManager {
private static final ConcurrentHashMap<SModelReference, FastNodeFinder> ourFinders = new ConcurrentHashMap<SModelReference, FastNodeFinder>();
private static final ConcurrentHashMap<SRepository, StructureAspectChangeTracker> ourStructureChangeTrackers = new ConcurrentHashMap<SRepository, StructureAspectChangeTracker>();
private static final ConcurrentHashMap<SRepository, ModelLifecycleTracker> ourLifecycleTrackers = new ConcurrentHashMap<SRepository, ModelLifecycleTracker>();
private static final ModuleListener ourStructureAspectListener = new ModuleListener() {
@Override
public void structureAspectChanged(Set<SModuleReference> changedModules) {
// forget all finders, as it seems cheaper to re-create than to figure out their inter-dependencies
ArrayList<FastNodeFinder> finders = new ArrayList<FastNodeFinder>(ourFinders.values());
ourFinders.clear();
for (FastNodeFinder finder : finders) {
finder.dispose();
}
}
};
@NotNull
public static FastNodeFinder get(SModel model) {
SModelReference mr = model.getReference();
FastNodeFinder finder = ourFinders.get(mr);
if (finder == null) {
track(model.getRepository());
if (model instanceof FastNodeFinder.Factory) {
finder = ((FastNodeFinder.Factory) model).createNodeFinder(model);
}
if (finder == null) {
// use FNF implementation that doesn't track changes
finder = new BaseFastNodeFinder(model);
}
final FastNodeFinder existing = ourFinders.putIfAbsent(mr, finder);
if (existing != null) {
finder = existing;
}
}
return finder;
}
public static void dispose(SModel model) {
SModelReference mr = model.getReference();
FastNodeFinder finder = ourFinders.remove(mr);
if (finder != null) {
finder.dispose();
}
}
/*
* Repository tracking is superfluous in end-user environment with fixed languages (their structure aspects never change),
* but there's no way to tell this kind of environment.
*/
private static void track(SRepository repo) {
if (repo == null) {
return;
}
if (!ourStructureChangeTrackers.containsKey(repo)) {
StructureAspectChangeTracker tracker1 = new StructureAspectChangeTracker(null, ourStructureAspectListener);
ourStructureChangeTrackers.putIfAbsent(repo, tracker1);
if (tracker1 == ourStructureChangeTrackers.get(repo)) {
// if another thread got luck, we don't need to attach tracker1 to the repo
repo.addRepositoryListener(tracker1);
}
}
if (!ourLifecycleTrackers.containsKey(repo)) {
ModelLifecycleTracker tracker2 = new ModelLifecycleTracker();
ourLifecycleTrackers.putIfAbsent(repo, tracker2);
if (tracker2 == ourLifecycleTrackers.get(repo)) {
repo.addRepositoryListener(tracker2);
}
}
}
private static class ModelLifecycleTracker extends SRepositoryContentAdapter {
@Override
protected void startListening(SModel model) {
super.startListening(model);
model.addModelListener(this);
}
@Override
protected void stopListening(SModel model) {
model.removeModelListener(this);
super.stopListening(model);
}
@Override
public void beforeModelRemoved(SModule module, SModel model) {
super.beforeModelRemoved(module, model);
FastNodeFinderManager.dispose(model);
}
@Override
public void modelReplaced(SModel model) {
FastNodeFinderManager.dispose(model);
}
@Override
public void modelRenamed(SModule module, SModel model, SModelReference oldRef) {
FastNodeFinderManager.dispose(model);
}
@Override
public void modelLoaded(SModel model, boolean partially) {
FastNodeFinderManager.dispose(model);
}
@Override
public void modelUnloaded(SModel model) {
FastNodeFinderManager.dispose(model);
}
}
}