/* * 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.cells; import jetbrains.mps.logging.Logger; import jetbrains.mps.nodeEditor.LanguageRegistryHelper; import jetbrains.mps.openapi.editor.descriptor.EditorAspectDescriptor; import jetbrains.mps.openapi.editor.descriptor.EditorHintsSpecific; import jetbrains.mps.smodel.language.LanguageRegistry; import org.apache.log4j.LogManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.language.SAbstractConcept; import org.jetbrains.mps.openapi.module.SRepository; import org.jetbrains.mps.util.BreadthConceptHierarchyIterator; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Stream; /** * Selects the best matching instance of {@code T} given a concept and a set of editor hints. Traverses superconcept and superinterface hierarchy * in breadth-first order. * * @param <T> a class implementing {@link EditorHintsSpecific} */ public abstract class AbstractEditorHintsSpecificRegistry<T extends EditorHintsSpecific> { private static final Logger LOG = Logger.wrap(LogManager.getLogger(AbstractEditorHintsSpecificRegistry.class)); private static Comparator<EditorHintsSpecific> ourCompareByHintsSizeDescAndClassName; private static Comparator<EditorHintsSpecific> ourCompareByHintsSizeDesc; private static Comparator<EditorHintsSpecific> compareByHintsSizeDescAndClassName() { if (ourCompareByHintsSizeDescAndClassName == null) { ourCompareByHintsSizeDescAndClassName = (a, b) -> { if (a.getContextHints().size() == b.getContextHints().size()) { return a.getClass().getName().compareTo(b.getClass().getName()); } return b.getContextHints().size() - a.getContextHints().size(); }; } return ourCompareByHintsSizeDescAndClassName; } private static Comparator<EditorHintsSpecific> compareByHintsSizeDesc() { if (ourCompareByHintsSizeDesc == null) { ourCompareByHintsSizeDesc = (a, b) -> b.getContextHints().size() - a.getContextHints().size(); } return ourCompareByHintsSizeDesc; } @NotNull private final SRepository myRepository; public AbstractEditorHintsSpecificRegistry(@NotNull SRepository repository) { myRepository = repository; } @Nullable public T get(@NotNull SAbstractConcept concept) { Set<SAbstractConcept> processedConcepts = new HashSet<>(); List<T> resultList = new ArrayList<>(); for (SAbstractConcept next : new BreadthConceptHierarchyIterator(concept)) { if (!processedConcepts.add(next)) { continue; } T instanceForConcept = getForConcept(next); if (instanceForConcept != null) { if (isExactContextMatch(instanceForConcept)) { return instanceForConcept; } else { resultList.add(instanceForConcept); } } } if (resultList.isEmpty()) { return null; } Collections.sort(resultList, compareByHintsSizeDesc()); return resultList.get(0); } private T getForConcept(@NotNull SAbstractConcept concept) { return collectApplicableInstances(concept).sorted(compareByHintsSizeDescAndClassName()).reduce((result, nextElement) -> { if (nextElement.getContextHints().size() == result.getContextHints().size()) { LOG.error(getErrorMessage(nextElement, result)); } return result; }).orElse(null); } private boolean isExactContextMatch(EditorHintsSpecific instance) { return instance.getContextHints().containsAll(getCurrentContextHints()); } private boolean isApplicableInCurrentContext(EditorHintsSpecific instance) { return getCurrentContextHints().containsAll(instance.getContextHints()); } private Stream<T> collectApplicableInstances(@NotNull SAbstractConcept concept) { EditorAspectDescriptor aspectDescriptor = LanguageRegistryHelper.getEditorAspectDescriptor(LanguageRegistry.getInstance(myRepository), concept.getLanguage()); return aspectDescriptor == null ? Stream.empty() : get(aspectDescriptor, concept).filter(this::isApplicableInCurrentContext); } private String getErrorMessage(T additional, T chosen) { String context = ""; for (String contextHint : getCurrentContextHints()) { if (!context.isEmpty()) { context += ", "; } context += contextHint; } return getErrorMessage(additional, chosen, context); } @NotNull protected abstract Stream<T> get(@NotNull EditorAspectDescriptor aspectDescriptor, @NotNull SAbstractConcept concept); protected abstract Collection<String> getCurrentContextHints(); protected abstract String getErrorMessage(T additional, T chosen, String context); }