/* * Copyright 2003-2015 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.nodeEditor; import jetbrains.mps.openapi.editor.descriptor.EditorAspectDescriptor; import jetbrains.mps.smodel.language.LanguageRuntime; import org.apache.log4j.LogManager; import org.jetbrains.annotations.NotNull; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * <p> * Maintains a cache of editor-aspect "contributions" ({@link jetbrains.mps.openapi.editor.descriptor.ConceptEditor}s, * {@link jetbrains.mps.openapi.editor.descriptor.ConceptEditorComponent}s, etc.). * <p> * Contributions are looked up using a key of type {@code KeyT}, which is usually a {@link org.jetbrains.mps.openapi.language.SAbstractConcept} by itself or * bundled with some extra data further identifying the contribution. Contributions are collected from the owning editor descriptor's language and languages * which extend this language (directly or indirectly). * <p> * The maintained cache is invalidated by {@link ValidEditorDescriptorsCache#cleanCaches(Iterable)}. * * @param <KeyT> type of the identifier of a contribution * @param <ContributionT> contribution type */ abstract class EditorAspectContributionsCache<KeyT, ContributionT> { private static final jetbrains.mps.logging.Logger LOG = jetbrains.mps.logging.Logger.wrap(LogManager.getLogger(EditorAspectContributionsCache.class)); private final Map<KeyT, Map<String, Collection<ContributionT>>> myCache = new HashMap<>(); @NotNull private final LanguageRuntime myLanguageRuntime; EditorAspectContributionsCache(@NotNull LanguageRuntime languageRuntime) { myLanguageRuntime = languageRuntime; } /** * Returns contributions for {@code key} from the owner language and all its extending languages. * * @param key the key of the contribution * @return a non-null, possibly empty, collection of contributions in an unspecified order. */ @NotNull public Collection<ContributionT> get(KeyT key) { return concatValues(getOrCompute(key)); } /** * Returns contributions for {@code key} from the owner language and all its extending languages but restricted to languages whose namespaces are contained in * {@code languageNamespaces}. * * @param key the key of the contribution * @param languageNamespaces a set of language namespaces/fully qualified names * @return a non-null, possibly empty, collection of contributions in an unspecified order. */ @NotNull Collection<ContributionT> getInLanguages(KeyT key, Set<String> languageNamespaces) { return filterKeysAndConcatValues(getOrCompute(key), languageNamespaces); } private Map<String, Collection<ContributionT>> getOrCompute(KeyT key) { Map<String, Collection<ContributionT>> values = myCache.get(key); if (values == null) { values = computeValues(key); myCache.put(key, values); } return values; } private static <T> Collection<T> concatValues(Map<?, Collection<T>> map) { return map.entrySet().stream().flatMap(e -> e.getValue().stream()).collect(Collectors.toList()); } private static <K, T> Collection<T> filterKeysAndConcatValues(Map<K, Collection<T>> map, Collection<K> wantedKeys) { return map.entrySet().stream() .filter(entry -> wantedKeys.contains(entry.getKey())) .flatMap(entry -> entry.getValue().stream()) .collect(Collectors.toList()); } public void clear() { myCache.clear(); } private Map<String, Collection<ContributionT>> computeValues(KeyT key) { Map<String, Collection<ContributionT>> result = new HashMap<>(); putIfNotEmpty(result, myLanguageRuntime.getNamespace(), getDeclaredContributions(myLanguageRuntime, key)); for (LanguageRuntime extendingLanguage : myLanguageRuntime.getExtendingLanguages()) { putIfNotEmpty(result, extendingLanguage.getNamespace(), getDeclaredContributions(extendingLanguage, key)); } return result; } private static <KeyT, ValueT> void putIfNotEmpty(Map<KeyT, Collection<ValueT>> map, KeyT key, Collection<ValueT> values) { if (!values.isEmpty()) { map.put(key, values); } } @NotNull private Collection<ContributionT> getDeclaredContributions(LanguageRuntime languageRuntime, KeyT key) { EditorAspectDescriptor editorAspect = LanguageRegistryHelper.getEditorAspectDescriptor(languageRuntime); if (editorAspect == null) { return Collections.emptyList(); } Collection<ContributionT> declaredContributions; try { declaredContributions = getDeclaredContributions(editorAspect, key); } catch (NoClassDefFoundError error) { LOG.error("Failed to get contributions from editor aspect descriptor " + editorAspect, error); declaredContributions = Collections.emptyList(); } return declaredContributions; } /** * Retrieve contributions declared directly in the model represented by {@code descriptor} and matching {@code key}. * @param descriptor the model to look in * @param key the key * @return a non-null, possibly empty, collection */ @NotNull protected abstract Collection<ContributionT> getDeclaredContributions(EditorAspectDescriptor descriptor, KeyT key); }