/* * 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.smodel.adapter.ids.SConceptId; import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory; import jetbrains.mps.smodel.language.ConceptRegistry; import jetbrains.mps.smodel.language.LanguageRegistry; import jetbrains.mps.smodel.language.LanguageRegistryListener; import jetbrains.mps.smodel.language.LanguageRuntime; import jetbrains.mps.smodel.runtime.ConceptDescriptor; import jetbrains.mps.smodel.runtime.StructureAspectDescriptor; import jetbrains.mps.util.IterableUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.language.SAbstractConcept; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * Based on generated structure descriptors */ public class ConceptDescendantsCache implements CoreComponent { private static ConceptDescendantsCache INSTANCE; private final MPSModuleRepository myModuleRepository; private final LanguageRegistry myLanguageRegistry; private final Map<LanguageRuntime, Set<ConceptDescriptor>> myLoadedLanguageToConceptsMap = new HashMap<LanguageRuntime, Set<ConceptDescriptor>>(); private final Set<LanguageRuntime> myNotProcessedRuntimes = new LinkedHashSet<LanguageRuntime>(); private final LanguageRegistryListener myLanguageRegistryListener = new LanguageRegistryListener() { @Override public void afterLanguagesLoaded(Iterable<LanguageRuntime> languages) { synchronized (myNotProcessedRuntimes) { myNotProcessedRuntimes.addAll(IterableUtil.asCollection(languages)); } } @Override public void beforeLanguagesUnloaded(Iterable<LanguageRuntime> languages) { HashSet<LanguageRuntime> c = new HashSet<LanguageRuntime>(IterableUtil.asCollection(languages)); synchronized (myNotProcessedRuntimes) { LinkedList<LanguageRuntime> notYetLoaded = new LinkedList<LanguageRuntime>(myNotProcessedRuntimes); notYetLoaded.retainAll(c); myNotProcessedRuntimes.removeAll(notYetLoaded); c.removeAll(notYetLoaded); } unloadConcepts(c); } }; // SConceptId used as key here (instead of SConcept) because of hashCode() method implementation for sub-classes of SConcept. // Currently all implementors return 0 from the hashCode() method. // Values are unmodifiable linked hash sets to guarantee iteration order. // TODO: use SConcept as a key after reimplementing SConcept.hasCode() method properly. private final ConcurrentMap<SAbstractConcept, Set<SAbstractConcept>> myDescendantsCache = new ConcurrentHashMap<>(); private void loadConcepts(Collection<LanguageRuntime> languages) { for (LanguageRuntime language : languages) { myLoadedLanguageToConceptsMap.put(language, getConcepts(language)); for (ConceptDescriptor concept : myLoadedLanguageToConceptsMap.get(language)) { loadConcept(concept); } } } private void unloadConcepts(Collection<LanguageRuntime> languages) { for (LanguageRuntime language : languages) { Set<ConceptDescriptor> concepts = myLoadedLanguageToConceptsMap.get(language); if (concepts == null) { throw new IllegalArgumentException("No concepts registered for the language " + language); } for (ConceptDescriptor concept : concepts) { unloadConcept(concept); } myLoadedLanguageToConceptsMap.remove(language); } } private void loadConcept(ConceptDescriptor concept) { List<SConceptId> pids = concept.getParentsIds(); SAbstractConcept abstractConcept = MetaAdapterFactory.getAbstractConcept(concept); for (SConceptId id : pids) { SAbstractConcept parentConcept = MetaAdapterFactory.getAbstractConcept(ConceptRegistry.getInstance().getConceptDescriptor(id)); myDescendantsCache.compute(parentConcept, (key, values) -> add(values, abstractConcept)); } } private void unloadConcept(ConceptDescriptor concept) { List<SConceptId> pids = concept.getParentsIds(); SAbstractConcept abstractConcept = MetaAdapterFactory.getAbstractConcept(concept); for (SConceptId id : pids) { SAbstractConcept parentConcept = MetaAdapterFactory.getAbstractConcept(ConceptRegistry.getInstance().getConceptDescriptor(id)); myDescendantsCache.compute(parentConcept, (key, values) -> remove(values, abstractConcept)); } } @NotNull private static Set<SAbstractConcept> add(@Nullable Set<SAbstractConcept> values, @NotNull SAbstractConcept abstractConcept) { // linked guarantees iteration order Set<SAbstractConcept> descendants = new LinkedHashSet<>(values == null ? Collections.emptySet() : values); descendants.add(abstractConcept); return Collections.unmodifiableSet(descendants); } @Nullable private static Set<SAbstractConcept> remove(@Nullable Set<SAbstractConcept> values, @NotNull SAbstractConcept abstractConcept) { if (values == null) { return null; } Set<SAbstractConcept> descendants = new LinkedHashSet<>(values); if (!descendants.remove(abstractConcept)) { return values; } if (descendants.isEmpty()) { return null; } return Collections.unmodifiableSet(descendants); } public ConceptDescendantsCache(MPSModuleRepository moduleRepository, LanguageRegistry languageRegistry) { myModuleRepository = moduleRepository; myLanguageRegistry = languageRegistry; } @Override public void init() { if (INSTANCE != null) { throw new IllegalStateException("double initialization"); } INSTANCE = this; myLanguageRegistry.addRegistryListener(myLanguageRegistryListener); } @Override public void dispose() { myLanguageRegistry.removeRegistryListener(myLanguageRegistryListener); INSTANCE = null; } public static ConceptDescendantsCache getInstance() { return INSTANCE; } /** * Collect all descendant concepts * Iteration order is guaranteed to be stable * * @param concept concept to start from * @return non-empty set of descendant concepts including the one supplied. */ public Set<SAbstractConcept> getDescendants(SAbstractConcept concept) { loadConceptsFromNotProcessedRuntimes(); //note that linked here guarantees the iteration order Set<SAbstractConcept> result = new LinkedHashSet<SAbstractConcept>(); collectDescendants(concept, result); return result; } public Set<SAbstractConcept> getDirectDescendants(SAbstractConcept concept) { loadConceptsFromNotProcessedRuntimes(); return getDirectDescendantsInternal(concept); } private void loadConceptsFromNotProcessedRuntimes() { synchronized (myNotProcessedRuntimes) { if (!myNotProcessedRuntimes.isEmpty()) { loadConcepts(myNotProcessedRuntimes); myNotProcessedRuntimes.clear(); } } } private void collectDescendants(SAbstractConcept concept, Set<SAbstractConcept> result) { if (result.contains(concept)) return; result.add(concept); for (SAbstractConcept descendant : getDirectDescendantsInternal(concept)) { collectDescendants(descendant, result); } } @NotNull private Set<SAbstractConcept> getDirectDescendantsInternal(SAbstractConcept concept) { myModuleRepository.getModelAccess().checkReadAccess(); Set<SAbstractConcept> result = myDescendantsCache.get(concept); return result != null ? result : Collections.<SAbstractConcept>emptySet(); } private Set<ConceptDescriptor> getConcepts(LanguageRuntime languageRuntime) { StructureAspectDescriptor structureDescriptor = languageRuntime.getAspect(StructureAspectDescriptor.class); if (structureDescriptor == null) { return Collections.emptySet(); } return new LinkedHashSet<ConceptDescriptor>(structureDescriptor.getDescriptors()); } }