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