/*
* 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.nodeEditor.cells;
import jetbrains.mps.logging.Logger;
import jetbrains.mps.nodeEditor.AbstractDefaultEditor;
import jetbrains.mps.openapi.editor.EditorContext;
import jetbrains.mps.openapi.editor.cells.EditorCell;
import jetbrains.mps.openapi.editor.cells.EditorCellContext;
import jetbrains.mps.openapi.editor.cells.EditorCellFactory;
import jetbrains.mps.openapi.editor.descriptor.ConceptEditor;
import jetbrains.mps.openapi.editor.descriptor.ConceptEditorComponent;
import jetbrains.mps.openapi.editor.descriptor.EditorAspectDescriptor;
import jetbrains.mps.openapi.editor.menus.transformation.SNodeLocation;
import jetbrains.mps.smodel.language.ConceptRegistry;
import jetbrains.mps.smodel.runtime.ConceptDescriptor;
import jetbrains.mps.util.SNodeOperations;
import org.apache.log4j.LogManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.language.SAbstractConcept;
import org.jetbrains.mps.openapi.language.SConcept;
import org.jetbrains.mps.openapi.model.SNode;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
/**
* User: shatalin
* Date: 4/24/13
*/
public class EditorCellFactoryImpl implements EditorCellFactory {
private static final Logger LOG = Logger.wrap(LogManager.getLogger(EditorCellFactoryImpl.class));
private static final EditorCellContext DEFAULT_CELL_CONTEXT = new EditorCellContext() {
@Override
public Collection<String> getHints() {
return Collections.emptySet();
}
@Override
public boolean hasContextHint(String hint) {
return false;
}
};
public static final String BASE_COMMENT_HINT = "jetbrains.mps.lang.core.editor.BaseEditorContextHints.comment";
public static final String BASE_REFLECTIVE_EDITOR_HINT = "jetbrains.mps.lang.core.editor.BaseEditorContextHints.reflectiveEditor";
private final EditorContext myEditorContext;
private Deque<EditorCellContextImpl> myCellContextStack;
private Map<SNode, Set<Class<? extends ConceptEditor>>> myUsedEditors = new HashMap<>();
private Map<EditorCellContext, Map<SConcept, Map<Collection<Class<? extends ConceptEditor>>, ConceptEditor>>> myEditorsCache = new HashMap<>();
private Map<EditorCellContext, Map<SConcept, Map<String, ConceptEditorComponent>>> myEditorComponentsCache = new HashMap<>();
public EditorCellFactoryImpl(EditorContext editorContext) {
myEditorContext = editorContext;
}
@Override
public EditorCell createEditorCell(SNode node, boolean isInspector, @NotNull Class<? extends ConceptEditor> excludedEditor) {
Set<Class<? extends ConceptEditor>> set;
if (myUsedEditors.containsKey(node)) {
set = myUsedEditors.get(node);
} else {
set = new HashSet<>();
myUsedEditors.put(node, set);
}
set.add(excludedEditor);
EditorCell editorCell = createEditorCell_internal(node, isInspector, Collections.unmodifiableSet(set));
set.remove(excludedEditor);
if (set.isEmpty()) {
myUsedEditors.remove(node);
}
return editorCell;
}
@Override
public EditorCell createEditorCell(SNode node, boolean isInspector) {
return createEditorCell_internal(node, isInspector, Collections.emptySet());
}
private EditorCell createEditorCell_internal(SNode node, boolean isInspector, @NotNull Set<Class<? extends ConceptEditor>> excludedEditors) {
boolean isPushReflectiveEditorHintInContext = getCellContext().getHints().contains(BASE_REFLECTIVE_EDITOR_HINT);
SConcept concept = node.getConcept();
ConceptEditor editor = isPushReflectiveEditorHintInContext ? null : getCachedEditor(concept, excludedEditors);
EditorCell result = null;
if (editor != null) {
try {
result = createCell(node, isInspector, editor);
assert result.isBig() : "Non-big " + (isInspector ? "inspector " : "") + "cell was created by " + editor.getClass().getName() + " ConceptEditor.";
} catch (RuntimeException | AssertionError | NoClassDefFoundError e) {
LOG.warning("Failed to create cell for node: " + SNodeOperations.getDebugText(node) + " using default editor", e, node);
}
}
if (result == null) {
boolean shouldShowInterfaceEditor = concept.isValid() && concept.isAbstract() && !isPushReflectiveEditorHintInContext;
editor = shouldShowInterfaceEditor ? new DefaultInterfaceEditor(getCellContext()) : AbstractDefaultEditor.createEditor(node);
result = createCell(node, isInspector, editor);
assert result.isBig() : "Non-big " + (isInspector ? "inspector " : "") + "cell was created by DefaultEditor: " + editor.getClass().getName();
}
//TODO: remove this call after MPS 3.5 - CellContext should be correctly set during editor cell creation process
result.setCellContext(getCellContext());
return result;
}
private EditorCell createCell(SNode node, boolean isInspector, ConceptEditor editor) {
return isInspector ? editor.createInspectedCell(myEditorContext, node) : editor.createEditorCell(myEditorContext, node);
}
@Override
public EditorCell createEditorComponentCell(SNode node, String editorComponentId) {
ConceptEditorComponent editorComponent = getCachedEditorComponent(node.getConcept(), editorComponentId);
EditorCell result = null;
if (editorComponent != null) {
try {
result = editorComponent.createEditorCell(myEditorContext, node);
} catch (RuntimeException | AssertionError | NoClassDefFoundError e) {
LOG.warning("Failed to create cell for node: " + SNodeOperations.getDebugText(node) + " using editor component: " + editorComponent.getClass(), e,
node);
}
}
if (result == null) {
result = new DefaultEditorComponent(editorComponentId).createEditorCell(myEditorContext, node);
}
//TODO: remove this call after MPS 3.5 - CellContext should be correctly set during editor cell creation process
result.setCellContext(getCellContext());
return result;
}
@Override
public EditorCellContext getCellContext() {
return myCellContextStack == null ? DEFAULT_CELL_CONTEXT : myCellContextStack.getLast();
}
@Override
public boolean hasCellContext() {
return myCellContextStack != null && !myCellContextStack.isEmpty();
}
@Override
public void pushCellContext() {
EditorCellContextImpl newCellContext = new EditorCellContextImpl(getCellContext());
if (myCellContextStack == null) {
myCellContextStack = new LinkedList<>();
}
myCellContextStack.addLast(newCellContext);
}
@Override
public void popCellContext() {
if (myCellContextStack == null || myCellContextStack.isEmpty()) {
throw new IllegalStateException("There is no CellContext in the stack");
}
myCellContextStack.removeLast();
if (myCellContextStack.isEmpty()) {
myCellContextStack = null;
}
}
@Override
public void addCellContextHints(String... hints) {
if (myCellContextStack == null) {
throw new IllegalStateException("There is no CellContext in the stack");
}
myCellContextStack.getLast().addHints(hints);
}
@Override
public void removeCellContextHints(String... hints) {
if (myCellContextStack == null) {
throw new IllegalStateException("There is no CellContext in the stack");
}
myCellContextStack.getLast().removeHints(hints);
}
public void setNodeLocation(SNodeLocation location) {
if (myCellContextStack == null) {
throw new IllegalStateException("There is no CellContext in the stack");
}
myCellContextStack.getLast().setNodeLocation(location);
}
private ConceptEditor getCachedEditor(SConcept concept, Collection<Class<? extends ConceptEditor>> excludedEditors) {
return myEditorsCache.computeIfAbsent(getCellContext(), c -> new HashMap<>()).computeIfAbsent(concept, c -> new HashMap<>()).computeIfAbsent(
excludedEditors, key -> new ConceptEditorRegistry(key).get(concept));
}
private ConceptEditorComponent getCachedEditorComponent(SConcept concept, String editorComponentId) {
return myEditorComponentsCache.computeIfAbsent(getCellContext(), c -> new HashMap<>()).computeIfAbsent(concept, c -> new HashMap<>()).computeIfAbsent(
editorComponentId, id -> new ConceptEditorComponentRegistry(id).get(concept));
}
private class ConceptEditorRegistry extends AbstractEditorRegistry<ConceptEditor> {
private final Collection<Class<? extends ConceptEditor>> myExcludedEditors;
private ConceptEditorRegistry(Collection<Class<? extends ConceptEditor>> excludedEditors) {
super(getCellContext(), myEditorContext.getRepository());
myExcludedEditors = excludedEditors;
}
@NotNull
@Override
protected Stream<ConceptEditor> get(@NotNull EditorAspectDescriptor aspectDescriptor, @NotNull SAbstractConcept concept) {
return aspectDescriptor.getEditors(concept).stream().filter(e -> !myExcludedEditors.contains(e.getClass()));
}
}
private class ConceptEditorComponentRegistry extends AbstractEditorRegistry<ConceptEditorComponent> {
private final String myEditorComponentId;
private ConceptEditorComponentRegistry(String editorComponentId) {
super(getCellContext(), myEditorContext.getRepository());
myEditorComponentId = editorComponentId;
}
@NotNull
@Override
protected Stream<ConceptEditorComponent> get(@NotNull EditorAspectDescriptor aspectDescriptor, @NotNull SAbstractConcept concept) {
return aspectDescriptor.getEditorComponents(concept, myEditorComponentId).stream();
}
}
private static class DefaultInterfaceEditor implements ConceptEditor {
private final EditorCellContext myCellContext;
private DefaultInterfaceEditor(EditorCellContext cellContext) {
myCellContext = cellContext;
}
@NotNull
@Override
public Collection<String> getContextHints() {
return Collections.emptyList();
}
@Override
public EditorCell createEditorCell(EditorContext context, SNode node) {
EditorCell_Error editorCell = new EditorCell_Error(context, node, " ");
editorCell.setCellId("Error");
editorCell.setBig(true);
editorCell.setCellContext(myCellContext);
return editorCell;
}
@Override
public EditorCell createInspectedCell(EditorContext context, SNode node) {
EditorCell_Constant editorCell = new EditorCell_Constant(context, node, SNodeOperations.getDebugText(node));
editorCell.setBig(true);
editorCell.setCellContext(myCellContext);
return editorCell;
}
}
private static class DefaultEditorComponent implements ConceptEditorComponent {
private final String myEditorComponentId;
private DefaultEditorComponent(String editorComponentId) {
myEditorComponentId = editorComponentId;
}
@NotNull
@Override
public Collection<String> getContextHints() {
return Collections.emptyList();
}
@Override
public EditorCell createEditorCell(EditorContext editorContext, SNode node) {
return new EditorCell_Error(editorContext, node, "editor component not found: " + myEditorComponentId);
}
}
}