/*
* Copyright 2003-2014 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.updater;
import com.intellij.openapi.project.Project;
import jetbrains.mps.ide.project.ProjectHelper;
import jetbrains.mps.nodeEditor.EditorManager;
import jetbrains.mps.nodeEditor.ReferencedNodeContext;
import jetbrains.mps.nodeEditor.SModelModificationsCollector;
import jetbrains.mps.nodeEditor.cells.EditorCellFactoryImpl;
import jetbrains.mps.nodeEditor.cells.EditorCell_Error;
import jetbrains.mps.nodeEditor.hintsSettings.ConceptEditorHintSettingsComponent;
import jetbrains.mps.nodeEditor.hintsSettings.ConceptEditorHintSettingsComponent.HintsState;
import jetbrains.mps.openapi.editor.EditorContext;
import jetbrains.mps.openapi.editor.cells.EditorCell;
import jetbrains.mps.openapi.editor.cells.EditorCellFactory;
import jetbrains.mps.openapi.editor.menus.transformation.SNodeLocation;
import jetbrains.mps.openapi.editor.update.AttributeKind;
import jetbrains.mps.openapi.editor.update.UpdateSession;
import jetbrains.mps.smodel.event.SModelEvent;
import jetbrains.mps.util.Computable;
import jetbrains.mps.util.Pair;
import jetbrains.mps.util.WeakSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeReference;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* User: shatalin
* Date: 03/09/14
*/
public class UpdateSessionImpl implements UpdateSession {
private static final String[] EMPTY_HINTS_ARRAY = new String[0];
@NotNull
private final UpdaterImpl myUpdater;
private SNode myNode;
private List<Pair<SNode, SNodeReference>> myModelModifications;
private String[] myInitialEditorHints = null;
private Map<SNode, WeakReference<EditorCell>> myBigCellsMap;
private Map<EditorCell, Set<SNode>> myRelatedNodes;
private Map<EditorCell, Set<SNodeReference>> myRelatedRefTargets;
private Map<Pair<SNodeReference, String>, WeakSet<EditorCell>> myCleanDependentCells;
private Map<Pair<SNodeReference, String>, WeakSet<EditorCell>> myDirtyDependentCells;
private Map<Pair<SNodeReference, String>, WeakSet<EditorCell>> myExistenceDependentCells;
private Map<SNodeReference, Collection<String>> myHintsForNodeMap = new HashMap<SNodeReference, Collection<String>>();
private UpdateInfoIndex myUpdateInfoIndex;
private UpdateInfoNode myCurrentUpdateInfo;
private EditorCellFactory myCellFactory;
private Map<AttributeKind, Deque<EditorCell>> myAttributeKind2Cell = new HashMap<>();
protected UpdateSessionImpl(@NotNull SNode node, List<SModelEvent> events, @NotNull UpdaterImpl updater, Map<SNode, WeakReference<EditorCell>> bigCellsMap,
Map<EditorCell, Set<SNode>> relatedNodes, Map<EditorCell, Set<SNodeReference>> relatedRefTargets,
Map<Pair<SNodeReference, String>, WeakSet<EditorCell>> cleanDependentCells, Map<Pair<SNodeReference, String>, WeakSet<EditorCell>> dirtyDependentCells,
Map<Pair<SNodeReference, String>, WeakSet<EditorCell>> existenceDependentCells, UpdateInfoIndex updateInfoIndex) {
myNode = node;
myModelModifications = new SModelModificationsCollector(events).getModifications();
myUpdater = updater;
myBigCellsMap = bigCellsMap;
myRelatedNodes = relatedNodes;
myRelatedRefTargets = relatedRefTargets;
myCleanDependentCells = cleanDependentCells;
myDirtyDependentCells = dirtyDependentCells;
myExistenceDependentCells = existenceDependentCells;
myUpdateInfoIndex = updateInfoIndex;
}
@Override
public void registerDependencies(EditorCell cell, Iterable<SNode> nodes, Iterable<SNodeReference> refTargets) {
Set<SNode> registeredRelatedNodes = new HashSet<SNode>();
myRelatedNodes.put(cell, registeredRelatedNodes);
for (SNode nextNode : nodes) {
assert nextNode != null;
registeredRelatedNodes.add(nextNode);
}
Set<SNodeReference> registeredRefTargets = new HashSet<SNodeReference>();
myRelatedRefTargets.put(cell, registeredRefTargets);
for (SNodeReference nextRefTarget : refTargets) {
registeredRefTargets.add(nextRefTarget);
}
}
@Override
public void registerCleanDependency(EditorCell cell, Pair<SNodeReference, String> pair) {
WeakSet<EditorCell> dependentCells = myCleanDependentCells.get(pair);
if (dependentCells == null) {
dependentCells = new WeakSet<EditorCell>();
myCleanDependentCells.put(pair, dependentCells);
}
dependentCells.add(cell);
}
@Override
public void registerDirtyDependency(EditorCell cell, Pair<SNodeReference, String> pair) {
WeakSet<EditorCell> dependentCells = myDirtyDependentCells.get(pair);
if (dependentCells == null) {
dependentCells = new WeakSet<EditorCell>();
myDirtyDependentCells.put(pair, dependentCells);
}
dependentCells.add(cell);
}
@Override
public void registerExistenceDependency(EditorCell cell, Pair<SNodeReference, String> pair) {
WeakSet<EditorCell> dependentCells = myExistenceDependentCells.get(pair);
if (dependentCells == null) {
dependentCells = new WeakSet<EditorCell>();
myExistenceDependentCells.put(pair, dependentCells);
}
dependentCells.add(cell);
}
Pair<EditorCell, UpdateInfoIndex> performUpdate() {
myCurrentUpdateInfo = new UpdateInfoNode(ReferencedNodeContext.createNodeContext(getNode()));
EditorContext editorContext = getUpdater().getEditorContext();
getCellFactory().pushCellContext();
Pair<EditorCell, UpdateInfoIndex> result = new Pair<EditorCell, UpdateInfoIndex>(null, null);
try {
getCellFactory().addCellContextHints(getInitialEditorHints(editorContext));
String[] explicitHintsForNode = getExplicitHintsForNode(getNode());
if (explicitHintsForNode != null) {
getCellFactory().addCellContextHints(explicitHintsForNode);
}
result.o1 = EditorManager.getInstanceFromContext(editorContext).createRootCell(getNode(), getModelModifications(), getCurrentContext(),
editorContext.isInspector());
} finally {
getCellFactory().popCellContext();
result.o2 = new UpdateInfoIndex(myCurrentUpdateInfo);
myCurrentUpdateInfo = null;
}
return result;
}
void setInitialEditorHints(String[] initialHints) {
myInitialEditorHints = initialHints;
}
@NotNull
private String[] getInitialEditorHints(EditorContext editorContext) {
if (myInitialEditorHints != null) {
return myInitialEditorHints;
}
Project project = ProjectHelper.toIdeaProject(ProjectHelper.getProject(editorContext.getRepository()));
if (project == null) {
return EMPTY_HINTS_ARRAY;
}
HintsState state = ConceptEditorHintSettingsComponent.getInstance(project).getState();
return state.getEnabledHints().toArray(EMPTY_HINTS_ARRAY);
}
@Nullable
private String[] getExplicitHintsForNode(SNode node) {
if (myHintsForNodeMap == null || !myHintsForNodeMap.containsKey(node.getReference())) {
return null;
}
Collection<String> hints = myHintsForNodeMap.get(node.getReference());
return hints.toArray(new String[hints.size()]);
}
void setEditorHintsForNodeMap(Map<SNodeReference, Collection<String>> hintsForNodeMap) {
myHintsForNodeMap = hintsForNodeMap;
}
@Override
public EditorCell updateChildNodeCell(final SNode node) {
return updateChildNodeCell(node, new SNodeLocation.FromNode(node));
}
@Override
public EditorCell updateChildNodeCell(SNode node, @NotNull SNodeLocation location) {
getCellFactory().pushCellContext();
getCellFactory().setNodeLocation(location);
myCurrentUpdateInfo = new UpdateInfoNode(getCurrentContext().sameContextButAnotherNode(node), myCurrentUpdateInfo);
try {
final EditorContext editorContext = getUpdater().getEditorContext();
return runWithExplicitEditorHints(editorContext, node, new Computable<EditorCell>() {
@Override
public EditorCell compute() {
return EditorManager.getInstanceFromContext(editorContext).createEditorCell(getModelModifications(), getCurrentContext());
}
});
} finally {
myCurrentUpdateInfo = myCurrentUpdateInfo.getParent();
getCellFactory().popCellContext();
}
}
@Override
public EditorCell updateAttributeCell(AttributeKind attributeKind, EditorCell attributedCell, SNode attribute) {
if (attributeKind != AttributeKind.REFERENCE && getCurrentContext().hasRoles()) {
//Suppressing role attribute cell creation upon reference cells.
return attributedCell;
}
final EditorContext editorContext = getUpdater().getEditorContext();
getCellFactory().pushCellContext();
getCellFactory().removeCellContextHints(EditorCellFactoryImpl.BASE_REFLECTIVE_EDITOR_HINT);
final boolean isNodeAttribute = attributeKind == AttributeKind.NODE;
if (isNodeAttribute) {
// Special case: replacing currentUpdateInfo with new one for node attribute
//
// This is necessary to correctly reflect cell structure in UpdateInfo tree: node attribute cell is a parent cell of node cell.
//
// We should handle it here because of the current logic in {@link EditorManager#createEditorCell()} method: it first creates cell
// for the node itself and then handle attribute cell creation. Better approach: first create new cell for node attribute & handle node
// cell creation only at the moment we process [>attributed cell<] cell in attribute's editor. In this case such hack will not
// be necessary: UpdateInfo, representing attributed node, will be created as a part of child cell creation for attribute's cell (UpdateInfo).
myCurrentUpdateInfo = myCurrentUpdateInfo.replaceByNodeAttributeInfo(ReferencedNodeContext.createNodeAttributeContext(attribute));
} else {
myCurrentUpdateInfo = new UpdateInfoNode(getCurrentContext().sameContextButAnotherNode(attribute), myCurrentUpdateInfo);
}
try {
return runWithExplicitEditorHints(editorContext, attribute, () -> doCreateRoleAttributeCell(attributeKind, attributedCell, getCurrentContext()));
} finally {
getCellFactory().popCellContext();
if (!isNodeAttribute) {
myCurrentUpdateInfo = myCurrentUpdateInfo.getParent();
}
}
}
private EditorCell doCreateRoleAttributeCell(AttributeKind attributeKind, EditorCell cellWithRole, ReferencedNodeContext refContext) {
myAttributeKind2Cell.computeIfAbsent(attributeKind, k -> new LinkedList<>()).addFirst(cellWithRole);
// For the compatibility with Attribute concept editor.
// If the editor for sub-concept of Attribute was not specified then default one will be used, so
// providing the possibility to always call getCurrentAttributedCellWithRole() with AttributeKind.Node.class
// specified as a parameter.
if (attributeKind != AttributeKind.NODE) {
myAttributeKind2Cell.computeIfAbsent(AttributeKind.NODE, k -> new LinkedList<>()).addFirst(cellWithRole);
}
try {
return EditorManager.getInstanceFromContext(getUpdater().getEditorContext()).createEditorCell(getModelModifications(), refContext);
} finally {
assert myAttributeKind2Cell.get(attributeKind).removeFirst() == cellWithRole;
if (attributeKind != AttributeKind.NODE) {
assert myAttributeKind2Cell.get(AttributeKind.NODE).removeFirst() == cellWithRole;
}
}
}
@NotNull
@Override
public EditorCell getAttributedCell(AttributeKind attributeKind, SNode attribute) {
// Marking UpdateInfoNode of the attributed cell as "used". UpdateInfoNode will be attached as a child
// of the current UpdateInfoNode on first keepAttributedInfo() call.
myCurrentUpdateInfo.keepAttributedInfo();
return myAttributeKind2Cell.computeIfAbsent(attributeKind, k -> new LinkedList<>()).stream().findFirst().orElseGet(
() -> new EditorCell_Error(getUpdater().getEditorContext(), attribute, "<attributed cell not found>"));
}
@Override
public <T> T updateReferencedNodeCell(Computable<T> update, SNode node, String role) {
ReferencedNodeContext newContext = getCurrentContext().contextWithOneMoreReference(node, getCurrentContext().getNode(), role);
myCurrentUpdateInfo = new UpdateInfoNode(newContext, myCurrentUpdateInfo);
try {
return update.compute();
} finally {
myCurrentUpdateInfo = myCurrentUpdateInfo.getParent();
}
}
@NotNull
@Override
public EditorCellFactory getCellFactory() {
if (myCellFactory == null) {
myCellFactory = new EditorCellFactoryImpl(getUpdater().getEditorContext());
}
return myCellFactory;
}
private ReferencedNodeContext getCurrentContext() {
return myCurrentUpdateInfo.getContext();
}
public void registerAsBigCell(EditorCell cell) {
myBigCellsMap.put(cell.getSNode(), new WeakReference<EditorCell>(cell));
}
@Nullable
public List<Pair<SNode, SNodeReference>> getModelModifications() {
return myModelModifications;
}
@NotNull
public SNode getNode() {
return myNode;
}
@NotNull
protected UpdaterImpl getUpdater() {
return myUpdater;
}
private <T> T runWithExplicitEditorHints(EditorContext editorContext, SNode node, Computable<T> cellCreator) {
String[] explicitHintsForNode = getExplicitHintsForNode(node);
if (explicitHintsForNode != null) {
getCellFactory().pushCellContext();
getCellFactory().addCellContextHints(explicitHintsForNode);
}
try {
return cellCreator.compute();
} finally {
if (explicitHintsForNode != null) {
getCellFactory().popCellContext();
}
}
}
public void reuseChildInfo(ReferencedNodeContext childContext) {
UpdateInfoNode updateInfoNode = myUpdateInfoIndex.remove(childContext);
assert updateInfoNode != null;
myCurrentUpdateInfo = myCurrentUpdateInfo.replace(updateInfoNode);
}
}