/*
* 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);
}