package jetbrains.mps.vcs.diff.ui.merge;
/*Generated by MPS */
import javax.swing.JPanel;
import javax.swing.Icon;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.project.Project;
import org.jetbrains.mps.openapi.module.SRepository;
import jetbrains.mps.vcs.diff.merge.MergeSession;
import jetbrains.mps.vcs.diff.merge.MergeSessionState;
import org.jetbrains.mps.openapi.model.SNodeId;
import org.jetbrains.mps.openapi.model.SModel;
import com.intellij.ui.JBSplitter;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import java.util.List;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.ActionToolbar;
import com.intellij.openapi.diff.ex.DiffStatusBar;
import com.intellij.openapi.diff.impl.util.TextDiffType;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import jetbrains.mps.vcs.diff.ui.common.GoToNeighbourRootActions;
import java.util.Set;
import jetbrains.mps.vcs.diff.changes.ModelChange;
import jetbrains.mps.internal.collections.runtime.SetSequence;
import java.util.HashSet;
import com.intellij.diff.merge.TextMergeRequest;
import java.awt.BorderLayout;
import jetbrains.mps.ide.project.ProjectHelper;
import jetbrains.mps.vcs.diff.ui.common.DiffModelUtil;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import jetbrains.mps.vcs.diff.ui.MetadataUtil;
import java.util.ArrayList;
import com.intellij.openapi.actionSystem.Separator;
import com.intellij.ui.ScrollPaneFactory;
import java.awt.Dimension;
import com.intellij.openapi.util.DimensionService;
import org.jetbrains.annotations.Nullable;
import jetbrains.mps.internal.collections.runtime.Sequence;
import jetbrains.mps.internal.collections.runtime.IWhereFilter;
import jetbrains.mps.vcs.diff.changes.MetadataChange;
import jetbrains.mps.smodel.ModelAccessHelper;
import jetbrains.mps.util.Computable;
import jetbrains.mps.vcs.diff.merge.MergeTemporaryModel;
import jetbrains.mps.smodel.CopyUtil;
import jetbrains.mps.extapi.model.SModelBase;
import org.jetbrains.annotations.NotNull;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import jetbrains.mps.vcs.diff.ui.common.DiffModelTree;
import jetbrains.mps.internal.collections.runtime.ISelector;
import jetbrains.mps.internal.collections.runtime.ITranslator2;
import jetbrains.mps.util.NameUtil;
import javax.swing.event.TreeSelectionListener;
import javax.swing.event.TreeSelectionEvent;
import jetbrains.mps.workbench.action.BaseAction;
import java.util.Arrays;
import jetbrains.mps.vcs.diff.changes.ChangeType;
import com.intellij.ui.SimpleTextAttributes;
import jetbrains.mps.vcs.diff.changes.AddRootChange;
import jetbrains.mps.vcs.diff.changes.DeleteRootChange;
import jetbrains.mps.vcs.diff.ui.common.ChangeColors;
public class MergeModelsPanel extends JPanel {
public static final Icon APPLY_NON_CONFLICTS = AllIcons.Diff.ApplyNotConflicts;
public static final Icon RESET = AllIcons.Actions.Rollback;
private final Project myProject;
private final SRepository myProjectRepository;
private MergeSession myMergeSession;
private MergeSession myMetadataMergeSession;
private MergeSessionState myInitialState;
private MergeSessionState myMetadataInitialState;
private SNodeId myRootId;
private ISaveMergedModel mySaver = new ISaveMergedModel() {
public boolean save(MergeModelsPanel dialog, SModel resultModel) {
return false;
}
};
private MergeModelsPanel.MergeModelsTree myMergeTree;
private JBSplitter myPanel = new JBSplitter(true, 0.25f);
private MergeRootsPane myMergeRootsPane = null;
private final JComponent myNoRootPanel = new JLabel("Select root to merge", SwingConstants.CENTER);
private List<AnAction> myToolbarActions;
private ActionToolbar myToolbar;
private DiffStatusBar myStatusBar = new DiffStatusBar(TextDiffType.DIFF_TYPES);
private DefaultActionGroup myActionGroup;
private GoToNeighbourRootActions myGoToNeighbourRootActions;
private String[] myContentTitles = new String[0];
private Set<ModelChange> myAppliedMetadataChanges = SetSequence.fromSet(new HashSet<ModelChange>());
public MergeModelsPanel(Project project, final SModel baseModel, final SModel mineModel, final SModel repoModel, TextMergeRequest request) {
super(new BorderLayout());
myProject = project;
myContentTitles = request.getContentTitles().toArray(myContentTitles);
assert myContentTitles.length == 3;
// FIXME code below requires thorough refactoring. Models that come here are IMO loaded from disk and are not
// attached to any repository, hence there's no reason to grab lock to deal with them. OTOH, there's code that
// registers and exposes model artifacts with a temp module, which is part of global repository now and hence
// requires model lock.
myProjectRepository = ProjectHelper.getProjectRepository(project);
assert myProjectRepository != null;
myProjectRepository.getModelAccess().runReadAction(new Runnable() {
public void run() {
myMergeSession = MergeSession.createMergeSession(baseModel, mineModel, repoModel);
myInitialState = myMergeSession.getCurrentState();
}
});
myProjectRepository.getModelAccess().runWriteAction(new Runnable() {
public void run() {
DiffModelUtil.renameModelAndRegister(myMergeSession.getBaseModel(), "base");
DiffModelUtil.renameModelAndRegister(myMergeSession.getMyModel(), "mine");
DiffModelUtil.renameModelAndRegister(myMergeSession.getRepositoryModel(), "repo");
DiffModelUtil.renameModelAndRegister(myMergeSession.getResultModel(), "result");
}
});
if (ListSequence.fromList(myMergeSession.getMetadataChanges()).isNotEmpty()) {
myProjectRepository.getModelAccess().runWriteAction(new Runnable() {
public void run() {
SModel baseMetaModel = MetadataUtil.createMetadataModel(myMergeSession.getBaseModel(), "metadata_base", false);
SModel mineMetaModel = MetadataUtil.createMetadataModel(myMergeSession.getMyModel(), "metadata_mine", false);
SModel repoMetaModel = MetadataUtil.createMetadataModel(myMergeSession.getRepositoryModel(), "metadata_repo", false);
myMetadataMergeSession = MergeSession.createMergeSession(baseMetaModel, mineMetaModel, repoMetaModel);
myMetadataInitialState = myMetadataMergeSession.getCurrentState();
DiffModelUtil.renameModelAndRegister(myMetadataMergeSession.getResultModel(), "result");
}
});
}
myMergeSession.installResultModelListener();
myToolbarActions = ListSequence.fromListAndArray(new ArrayList<AnAction>(), new ResetState(this), new MergeNonConflictingRoots(this), Separator.getInstance(), AcceptYoursTheirs.yoursInstance(this), AcceptYoursTheirs.theirsInstance(this));
myActionGroup = new DefaultActionGroup(myToolbarActions);
init();
}
public List<AnAction> getToolbarActions() {
return myToolbarActions;
}
public void setSaver(ISaveMergedModel saver) {
mySaver = saver;
}
protected boolean saveModel(SModel resultModel) {
return mySaver.save(this, resultModel);
}
protected void init() {
myPanel.setSplitterProportionKey(getClass().getName() + "ModelTreeSplitter");
myMergeTree = new MergeModelsPanel.MergeModelsTree();
myPanel.setFirstComponent(ScrollPaneFactory.createScrollPane(myMergeTree));
myPanel.setSecondComponent(myNoRootPanel);
myGoToNeighbourRootActions = new MergeModelsPanel.MyGoToNeighbourRootActions();
myGoToNeighbourRootActions.previous().registerCustomShortcutSet(GoToNeighbourRootActions.PREV_ROOT_SHORTCUT, this);
myGoToNeighbourRootActions.next().registerCustomShortcutSet(GoToNeighbourRootActions.NEXT_ROOT_SHORTCUT, this);
this.add(myPanel, BorderLayout.CENTER);
this.add(myStatusBar, BorderLayout.SOUTH);
final Dimension size = DimensionService.getInstance().getSize(getDimensionServiceKey());
if (size == null) {
this.setPreferredSize(new Dimension(500, 450));
}
}
public String getDimensionServiceKey() {
return getClass().getName();
}
@Nullable
public JComponent getPreferredFocusedComponent() {
return myMergeTree;
}
public boolean saveResults() {
// true - everything is OK
// false - saving was cancelled
applyMetadataChanges();
int result = MergeConfirmation.showMergeConfirmationAndTakeAction(this, myMergeSession, Sequence.fromIterable(myMergeSession.getAllChanges()).where(new IWhereFilter<ModelChange>() {
public boolean accept(ModelChange ch) {
return !(ch instanceof MetadataChange);
}
}), myMetadataMergeSession, (myMetadataMergeSession == null ? null : myMetadataMergeSession.getAllChanges()));
if (result == MergeConfirmation.RETURN) {
return false;
}
if (result == MergeConfirmation.RESOLVE_AUTOMATICALLY) {
myProjectRepository.getModelAccess().executeCommand(new Runnable() {
public void run() {
mergeNonConflictingRoots();
}
});
}
if (saveModel(getResultModelWithFixedId())) {
unregisterModels();
return true;
}
return false;
}
public void dispose() {
if (myMergeRootsPane != null) {
myMergeRootsPane.dispose();
}
}
private SModel getResultModelWithFixedId() {
SModel resultModel = new ModelAccessHelper(myProjectRepository).runReadAction(new Computable<MergeTemporaryModel>() {
public MergeTemporaryModel compute() {
// copy to avoid problems with de-registration
jetbrains.mps.smodel.SModel resModel = CopyUtil.copyModel(as_ktyr7l_a0a0a1a0a0a0a0a0qb(myMergeSession.getResultModel(), SModelBase.class).getSModelInternal());
return new MergeTemporaryModel(resModel, false);
}
});
DiffModelUtil.restoreModelName(resultModel);
// fix???
for (SModel m : new SModel[]{myMergeSession.getMyModel(), myMergeSession.getRepositoryModel()}) {
DiffModelUtil.fixModelReferences(resultModel, m.getReference());
}
return resultModel;
}
private void unregisterModels() {
myProjectRepository.getModelAccess().runWriteAction(new Runnable() {
public void run() {
if (myMetadataMergeSession != null) {
DiffModelUtil.unregisterModel(myMetadataMergeSession.getResultModel());
MetadataUtil.dispose(myMetadataMergeSession.getRepositoryModel());
MetadataUtil.dispose(myMetadataMergeSession.getMyModel());
MetadataUtil.dispose(myMetadataMergeSession.getBaseModel());
}
DiffModelUtil.unregisterModel(myMergeSession.getResultModel());
DiffModelUtil.unregisterModel(myMergeSession.getRepositoryModel());
DiffModelUtil.unregisterModel(myMergeSession.getMyModel());
DiffModelUtil.unregisterModel(myMergeSession.getBaseModel());
}
});
}
/*package*/ void rebuildLater() {
myMergeTree.rebuildLater();
}
/*package*/ Project getProject() {
return myProject;
}
@Nullable
public SNodeId getNeighbourRoot(@NotNull SNodeId rootId, boolean next) {
return myMergeTree.getNeighbourRoot(rootId, next);
}
public void resetCurrentRoot() {
if (myMergeRootsPane == null) {
return;
}
myMergeRootsPane.unregisterShortcuts(this);
myPanel.setSecondComponent(myNoRootPanel);
myMergeRootsPane.dispose();
myMergeRootsPane = null;
myRootId = null;
myStatusBar.setText("");
applyMetadataChanges();
}
private void changeCurrentRoot(@Nullable final SNodeId rootId) {
if (myMergeRootsPane != null && myRootId == rootId) {
return;
}
applyMetadataChanges();
myRootId = rootId;
final MergeSession session = (rootId == null ? myMetadataMergeSession : myMergeSession);
myProjectRepository.getModelAccess().runReadAction(new Runnable() {
public void run() {
SNodeId nodeId = (rootId == null ? Sequence.fromIterable(myMetadataMergeSession.getAffectedRoots()).first() : rootId);
if (myMergeRootsPane == null) {
myMergeRootsPane = new MergeRootsPane(myProject, session, nodeId, myMergeTree.getNameForRoot(rootId), myContentTitles, myStatusBar);
DefaultActionGroup actionGroup = new DefaultActionGroup();
actionGroup.addAll(myMergeRootsPane.getActions());
ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, actionGroup, true);
myMergeRootsPane.registerShortcuts(MergeModelsPanel.this);
JPanel panel = new JPanel(new BorderLayout());
panel.add(toolbar.getComponent(), BorderLayout.NORTH);
panel.add(myMergeRootsPane.getPanel(), BorderLayout.CENTER);
myPanel.setSecondComponent(panel);
} else {
myMergeRootsPane.setRoodId(nodeId, session);
}
}
});
}
public void setCurrentRoot(@Nullable SNodeId rootId) {
myMergeTree.setSelected(rootId);
changeCurrentRoot(rootId);
}
@Nullable
public SNodeId getCurrentRoot() {
return myRootId;
}
public Iterable<ModelChange> getApplicableChangesInNonConflictingRoots() {
return Sequence.fromIterable(myMergeSession.getApplicableChangesInNonConflictingRoots()).where(new IWhereFilter<ModelChange>() {
public boolean accept(ModelChange it) {
return !(it instanceof MetadataChange);
}
});
}
public Iterable<ModelChange> getApplicableChangesInMetadata() {
return myMetadataMergeSession.getApplicableChangesInNonConflictingRoots();
}
public void mergeNonConflictingRoots() {
myMergeSession.applyChanges(getApplicableChangesInNonConflictingRoots());
if (myMetadataMergeSession != null) {
myMetadataMergeSession.applyChanges(getApplicableChangesInMetadata());
applyMetadataChanges();
}
}
public boolean hasNonConflictingRoots() {
return Sequence.fromIterable(getApplicableChangesInNonConflictingRoots()).isNotEmpty() || myMetadataMergeSession != null && Sequence.fromIterable(getApplicableChangesInMetadata()).isNotEmpty();
}
public boolean isAcceptYoursTheirsEnabled() {
return Sequence.fromIterable(getModelChangesForSelection()).where(new IWhereFilter<ModelChange>() {
public boolean accept(ModelChange ch) {
return !(myMergeSession.isChangeResolved(ch));
}
}).isNotEmpty() || myMetadataMergeSession != null && isMetadataSelected() && Sequence.fromIterable(myMetadataMergeSession.getAllChanges()).where(new IWhereFilter<ModelChange>() {
public boolean accept(ModelChange ch) {
return !(myMetadataMergeSession.isChangeResolved(ch));
}
}).isNotEmpty();
}
public void acceptVersionForSelectedRoots(boolean mine) {
applyUnresolvedChanges(myMergeSession, getModelChangesForSelection(), mine);
if (myMetadataMergeSession != null && isMetadataSelected()) {
applyUnresolvedChanges(myMetadataMergeSession, myMetadataMergeSession.getAllChanges(), mine);
applyMetadataChanges();
}
// XXX tree.rebuildNow as model command, really?
myProjectRepository.getModelAccess().executeCommand(new Runnable() {
public void run() {
myMergeTree.rebuildNow();
}
});
}
private void applyMetadataChanges() {
myProjectRepository.getModelAccess().executeCommand(new Runnable() {
public void run() {
if (myMetadataMergeSession != null) {
MetadataUtil.applyMetadataChanges(myMergeSession.getResultModel(), myMetadataMergeSession.getResultModel());
}
}
});
}
private boolean isMetadataSelected() {
return myMergeTree.getSelectedNodes(DiffModelTree.MetadataTreeNode.class, null).length == 1 || myMergeTree.getSelectedNodes(DiffModelTree.ModelTreeNode.class, null).length == 1;
}
private Iterable<ModelChange> getModelChangesForSelection() {
if (myMergeTree.getSelectedNodes(DiffModelTree.ModelTreeNode.class, null).length == 1) {
return Sequence.fromIterable(myMergeSession.getAllChanges()).where(new IWhereFilter<ModelChange>() {
public boolean accept(ModelChange ch) {
return !(ch instanceof MetadataChange);
}
});
} else {
return Sequence.fromIterable(Sequence.fromArray(myMergeTree.getSelectedNodes(DiffModelTree.RootTreeNode.class, null))).select(new ISelector<DiffModelTree.RootTreeNode, SNodeId>() {
public SNodeId select(DiffModelTree.RootTreeNode rtn) {
return rtn.getRootId();
}
}).where(new IWhereFilter<SNodeId>() {
public boolean accept(SNodeId root) {
return root != null;
}
}).translate(new ITranslator2<SNodeId, ModelChange>() {
public Iterable<ModelChange> translate(SNodeId root) {
return myMergeSession.getChangesForRoot(root);
}
});
}
}
private void applyUnresolvedChanges(final MergeSession session, Iterable<ModelChange> changes, boolean mine) {
final List<ModelChange> changesToApply = ListSequence.fromList(new ArrayList<ModelChange>());
final List<ModelChange> changesToExclude = ListSequence.fromList(new ArrayList<ModelChange>());
for (ModelChange change : Sequence.fromIterable(changes).where(new IWhereFilter<ModelChange>() {
public boolean accept(ModelChange ch) {
return !(session.isChangeResolved(ch));
}
})) {
if (mine == session.isMyChange(change)) {
ListSequence.fromList(changesToApply).addElement(change);
} else {
ListSequence.fromList(changesToExclude).addElement(change);
}
}
myProjectRepository.getModelAccess().executeCommand(new Runnable() {
public void run() {
session.applyChanges(changesToApply);
session.excludeChanges(changesToExclude);
}
});
}
/*package*/ void markMetadataChangesAsApplied(Iterable<ModelChange> changes) {
SetSequence.fromSet(myAppliedMetadataChanges).addSequence(Sequence.fromIterable(changes));
}
/*package*/ String[] getContentTitles() {
return myContentTitles;
}
/*package*/ MergeSession getMergeSession() {
return myMergeSession;
}
public void resetState() {
myMergeSession.restoreState(myInitialState);
if (myMetadataMergeSession != null) {
myMetadataMergeSession.restoreState(myMetadataInitialState);
}
rebuildLater();
}
/*package*/ static String generateUnresolvedChangesText(int totalChanges, int conflictingChanges) {
if (conflictingChanges != 0) {
String text = NameUtil.formatNumericalString(conflictingChanges, "conficting change");
if (totalChanges == conflictingChanges) {
return text;
} else {
return text + " of " + totalChanges + " total";
}
} else {
if (totalChanges == 0) {
return "All changes resolved";
} else {
return NameUtil.formatNumericalString(totalChanges, " change");
}
}
}
private class MyGoToNeighbourRootActions extends GoToNeighbourRootActions.GoToByTree {
public MyGoToNeighbourRootActions() {
super(myMergeTree);
}
@Nullable
@Override
protected SNodeId getCurrentNodeId() {
return getCurrentRoot();
}
@Override
public void setCurrentNodeId(@Nullable SNodeId nodeId) {
setCurrentRoot(nodeId);
}
}
private class MergeModelsTree extends DiffModelTree {
private MergeModelsTree() {
addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent event) {
}
});
}
@Override
protected Iterable<BaseAction> getRootActions() {
MergeModelsPanel md = MergeModelsPanel.this;
return Arrays.<BaseAction>asList(AcceptYoursTheirs.yoursInstance(md), AcceptYoursTheirs.theirsInstance(md));
}
@Override
protected void updateRootCustomPresentation(@NotNull DiffModelTree.RootTreeNode rootTreeNode) {
final MergeSession session = (rootTreeNode.getRootId() == null ? myMetadataMergeSession : myMergeSession);
List<ModelChange> changes = Sequence.fromIterable(((rootTreeNode.getRootId() == null ? myMetadataMergeSession.getAllChanges() : myMergeSession.getChangesForRoot(rootTreeNode.getRootId())))).where(new IWhereFilter<ModelChange>() {
public boolean accept(ModelChange ch) {
return !(session.isChangeResolved(ch));
}
}).toListSequence();
int conflictedCount = ListSequence.fromList(changes).where(new IWhereFilter<ModelChange>() {
public boolean accept(ModelChange ch) {
return Sequence.fromIterable(session.getConflictedWith(ch)).isNotEmpty();
}
}).count();
int nonConflictedCount = ListSequence.fromList(changes).count() - conflictedCount;
ChangeType compositeChangeType = null;
rootTreeNode.setTooltipText(generateUnresolvedChangesText(ListSequence.fromList(changes).count(), conflictedCount));
if (conflictedCount != 0) {
compositeChangeType = ChangeType.CONFLICTED;
rootTreeNode.setAdditionalText("with conflicts");
} else {
if (nonConflictedCount == 0) {
if (rootTreeNode.getRootId() != null && myMergeSession.getResultModel().getNode(rootTreeNode.getRootId()) == null) {
rootTreeNode.setTextStyle(SimpleTextAttributes.STYLE_STRIKEOUT);
} else {
rootTreeNode.setAdditionalText(null);
}
} else {
compositeChangeType = ChangeType.CHANGE;
if (ListSequence.fromList(changes).all(new IWhereFilter<ModelChange>() {
public boolean accept(ModelChange ch) {
return ch instanceof AddRootChange || ch instanceof DeleteRootChange;
}
})) {
compositeChangeType = ListSequence.fromList(changes).first().getType();
}
int myChangesCount = ListSequence.fromList(changes).where(new IWhereFilter<ModelChange>() {
public boolean accept(ModelChange ch) {
return session.isMyChange(ch);
}
}).count();
if (myChangesCount == nonConflictedCount) {
rootTreeNode.setAdditionalText("local");
} else if (myChangesCount == 0) {
rootTreeNode.setAdditionalText("remote");
} else {
rootTreeNode.setAdditionalText("both modified");
}
}
}
rootTreeNode.setColor((compositeChangeType == null ? null : ChangeColors.getForTree(compositeChangeType)));
}
@Override
protected Iterable<SModel> getModels() {
return Arrays.asList(myMergeSession.getBaseModel(), myMergeSession.getMyModel(), myMergeSession.getRepositoryModel());
}
@Override
protected Iterable<SNodeId> getAffectedRoots() {
return myMergeSession.getAffectedRoots();
}
@Override
protected boolean isMultipleRootNames() {
return true;
}
@Override
protected void onUnselect() {
resetCurrentRoot();
}
@Override
protected void onSelectRoot(@Nullable SNodeId rootId) {
changeCurrentRoot(rootId);
}
}
private static <T> T as_ktyr7l_a0a0a1a0a0a0a0a0qb(Object o, Class<T> type) {
return (type.isInstance(o) ? (T) o : null);
}
}