diffs) {
IMergeRunnable runnable = createMergeRunnable(mode, isLeftEditable(), isRightEditable(),
diffRelationshipComputer);
ICompareCopyCommand command = editingDomain.createCopyCommand(diffs,
mode.isLeftToRight(isLeftEditable(), isRightEditable()), mergerRegistry, runnable);
commandStack.execute(command);
}
/**
* A facade to get a more manageable command stack when undoing and redoing certain diffs.
*
* Maintains a list of diff changes and a map from each of those to a possible associated edit command.
* This allows to properly detect cases that we can't support and redo undone diffs.
*
*
* @author Philip Langer
*/
private class ManagedCommandStack {
private List> diffChangesList = Lists.newArrayList();
private Map, EditCommand> editCommands = new IdentityHashMap, EditCommand>();
private int nonRepeatableCommandCount = 0;
private ICompareCommandStack commandStack;
private boolean isChangeUndoneYet;
public ManagedCommandStack(ICompareCommandStack commandStack) {
this.commandStack = commandStack;
}
private void addChangedDiffs(Multimap changedDiffs) {
diffChangesList.add(changedDiffs);
}
private void addChangedDiffs(Multimap changedDiffs, EditCommand editCommand) {
addChangedDiffs(changedDiffs);
editCommands.put(changedDiffs, editCommand);
}
private int getChangedDiffsSize() {
return diffChangesList.size();
}
private void reverseDiffChanges() {
Collections.reverse(diffChangesList);
}
private List> getDiffChanges() {
return diffChangesList;
}
private EditCommand getEditCommand(Multimap diffChanges) {
return editCommands.get(diffChanges);
}
private void increaseNonRepeatableCommandCount() {
nonRepeatableCommandCount = nonRepeatableCommandCount + 1;
}
public int getNonRepeatableCommandsCount() {
return nonRepeatableCommandCount;
}
public int getUndoneCommandsCount() {
return getNonRepeatableCommandsCount() + getChangedDiffsSize();
}
public boolean hasUnrepeatableCommands() {
return getNonRepeatableCommandsCount() > 0;
}
public void undoUntilDiffsAreInTerminalState(List diffs) {
while (commandStack.canUndo() && any(diffs, IS_IN_TERMINAL_STATE)) {
Command undoCommand = commandStack.getUndoCommand();
// Keep track of undone changes
if (undoCommand instanceof AbstractCopyCommand) {
AbstractCopyCommand copyCommand = (AbstractCopyCommand)undoCommand;
addChangedDiffs(copyCommand.getChangedDiffs());
} else if (undoCommand instanceof EditCommand) {
EditCommand editCommand = (EditCommand)undoCommand;
addChangedDiffs(editCommand.getChangedDiffs(), editCommand);
} else if (isCompoundCommandContainingAbstractCopyCommand(undoCommand)) {
Command firstCommand = getFirstCommandFromCompoundCommand(undoCommand);
AbstractCopyCommand copyCmd = (AbstractCopyCommand)firstCommand;
addChangedDiffs(copyCmd.getChangedDiffs());
} else {
increaseNonRepeatableCommandCount();
}
commandStack.undo();
}
}
public void restoreCommandStack() {
for (int i = getUndoneCommandsCount(); i > 0; --i) {
commandStack.redo();
}
}
/**
* Performs a redo of all commands that have previously been undone with this managed command stack,
* except for the specified diffsToExclude
.
*
* @param diffsToExclude
* Diffs to exclude from redoing.
* @param mode
* The merge mode.
* @return whether we've needed to undo any of the diffsToExclude
.
*/
public boolean redoExcept(List diffsToExclude, MergeMode mode) {
isChangeUndoneYet = false;
reverseDiffChanges();
for (Multimap diffsToBeRestored : getDiffChanges()) {
redoExcept(diffsToBeRestored, diffsToExclude, mode);
}
return isChangeUndoneYet;
}
private void redoExcept(Multimap diffsToRestore, List diffsToExclude,
MergeMode mode) {
// If there is an edit command associated with these diff changes...
EditCommand editCommand = getEditCommand(diffsToRestore);
if (editCommand != null) {
// If any of the diffs changed by this edit command is the diff we are currently
// processing, then ignore this edit command and its associated diff.
Collection discardedDiffs = diffsToRestore.get(DISCARDED);
for (Diff diff : discardedDiffs) {
if (diffsToExclude.contains(diff)) {
return;
}
}
undoIfNotUndoneYet();
commandStack.execute(editCommand.recreate());
return;
}
// Remove any diffs that have changed state because of other command execution.
removeTerminalStateDiffs(diffsToRestore.values().iterator());
// If there are diff changes that need to be restored...
if (!diffsToRestore.values().isEmpty()) {
undoIfNotUndoneYet();
List diffsToMerge = Lists.newArrayList(diffsToRestore.get(MERGED));
List diffsToDiscard = Lists.newArrayList(diffsToRestore.get(DISCARDED));
if (mode == ACCEPT || mode == REJECT) {
redoDiffs(diffsToMerge, diffsToDiscard, ACCEPT, REJECT);
} else {
List diffsToBeCopiedLTR = Stream
.concat(diffsToMerge.stream().filter(fromSource(LEFT)),
diffsToDiscard.stream().filter(fromSource(RIGHT)))
.collect(Collectors.toList());
List diffsToBeCopiedRTL = Stream
.concat(diffsToMerge.stream().filter(fromSource(RIGHT)),
diffsToDiscard.stream().filter(fromSource(LEFT)))
.collect(Collectors.toList());
redoDiffs(diffsToBeCopiedLTR, diffsToBeCopiedRTL, LEFT_TO_RIGHT, RIGHT_TO_LEFT);
}
}
}
private void redoDiffs(List diffsToMerge, List diffsToDiscarded, MergeMode modeForMerged,
MergeMode modeForDiscarded) {
// If there are any diffs that need to be in the merged state...
if (!diffsToMerge.isEmpty()) {
// Processed those diffs.
executeCompareCopyCommand(commandStack, modeForMerged, diffsToMerge);
// Clean up any discarded diffs that are already in the terminal state.
removeTerminalStateDiffs(diffsToDiscarded.iterator());
}
// If there are any diffs that need to be in the discarded state...
if (!diffsToDiscarded.isEmpty()) {
// Process those diffs.
executeCompareCopyCommand(commandStack, modeForDiscarded, diffsToDiscarded);
}
}
private void undoIfNotUndoneYet() {
if (!isChangeUndoneYet) {
commandStack.undo();
isChangeUndoneYet = true;
}
}
private java.util.function.Predicate super Diff> fromSource(DifferenceSource source) {
return (diff) -> diff.getSource() == source;
}
private boolean isCompoundCommandContainingAbstractCopyCommand(Command command) {
Command firstCommand = getFirstCommandFromCompoundCommand(command);
return firstCommand instanceof AbstractCopyCommand;
}
private Command getFirstCommandFromCompoundCommand(Command possiblyCompoundCommand) {
Command command = null;
if (possiblyCompoundCommand instanceof CompoundCommand) {
final CompoundCommand compoundCommand = (CompoundCommand)possiblyCompoundCommand;
if (!compoundCommand.getCommandList().isEmpty()) {
command = compoundCommand.getCommandList().get(0);
}
}
return command;
}
private void removeTerminalStateDiffs(Iterator diffs) {
while (diffs.hasNext()) {
if (AbstractMerger.isInTerminalState(diffs.next())) {
diffs.remove();
}
}
}
}
protected void executeCompareCopyCommand(ICompareCommandStack commandStack, MergeMode mode,
List diffs) {
IMergeRunnable runnable = new MergeRunnableImpl(isLeftEditable(), isRightEditable(), mode,
diffRelationshipComputer);
ICompareCopyCommand command = editingDomain.createCopyCommand(diffs,
mode.isLeftToRight(isLeftEditable(), isRightEditable()), mergerRegistry, runnable);
commandStack.execute(command);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.ui.actions.BaseSelectionListenerAction#updateSelection(org.eclipse.jface.viewers.IStructuredSelection)
*/
@Override
protected boolean updateSelection(IStructuredSelection selection) {
addAll(selectedDifferences, getSelectedDifferences(selection));
if (this.adapterFactory != null) {
contextualizeTooltip();
}
// The action is enabled only if all the elements in the selection are diffs that will change state
// when this action is applied.
return !selectedDifferences.isEmpty() && selection.toList().size() == selectedDifferences.size();
}
/**
* {@inheritDoc}
*
* @see org.eclipse.ui.actions.BaseSelectionListenerAction#clearCache()
*/
@Override
protected void clearCache() {
selectedDifferences.clear();
}
protected Iterable getSelectedDifferences(IStructuredSelection selection) {
List> selectedObjects = selection.toList();
Iterable selectedAdapters = filter(selectedObjects, Adapter.class);
Iterable selectedNotifiers = transform(selectedAdapters, ADAPTER__TARGET);
Iterable selectedTreeNode = filter(selectedNotifiers, TreeNode.class);
Iterable selectedEObjects = transform(selectedTreeNode, IDifferenceGroup.TREE_NODE_DATA);
Iterable diffs = filter(selectedEObjects, Diff.class);
if (isEmpty(diffs)) {
diffs = filter(selectedObjects, Diff.class);
}
return getSelectedDifferences(diffs);
}
protected Predicate getStatePredicate() {
return new Predicate() {
public boolean apply(Diff diff) {
switch (diff.getState()) {
case DISCARDED:
switch (getSelectedMode()) {
case ACCEPT:
return true;
case LEFT_TO_RIGHT:
return diff.getSource() == LEFT;
case RIGHT_TO_LEFT:
return diff.getSource() == RIGHT;
default:
return false;
}
case MERGED:
switch (getSelectedMode()) {
case REJECT:
return true;
case RIGHT_TO_LEFT:
return diff.getSource() == LEFT;
case LEFT_TO_RIGHT:
return diff.getSource() == RIGHT;
default:
return false;
}
default:
return true;
}
}
};
}
protected Iterable getSelectedDifferences(Iterable diffs) {
ICompareCommandStack commandStack = editingDomain.getCommandStack();
// We can only re-process diffs in the terminal state if we have a command stack that supports
// suspending the delivery of notifications. So filter out diffs that are already in the terminal
// state.
if (!(commandStack instanceof TransactionalDualCompareCommandStack)) {
return filter(diffs, IS_NOT_IN_TERMINAL_STATE);
}
// Filter out diffs whose state would not be changed by this actions's selected mode.
return filter(diffs, getStatePredicate());
}
/**
* @param newValue
*/
public final void setEditingDomain(ICompareEditingDomain editingDomain) {
this.editingDomain = editingDomain;
clearCache();
setEnabled(editingDomain != null && updateSelection(getStructuredSelection()));
}
/**
* Set the adapter factory used by this action.
*
* @param adapterFactory
* adapter factory
*/
public final void setAdapterFactory(AdapterFactory adapterFactory) {
this.adapterFactory = adapterFactory;
if (adapterFactory != null) {
contextualizeTooltip();
}
}
/**
* @return the leftToRight
*/
protected final boolean isLeftToRight() {
return getSelectedMode().isLeftToRight(isLeftEditable(), isRightEditable());
}
/**
* Returns the cached selected differences.
*
* @return The cached selected differences.
*/
public List getSelectedDifferences() {
return selectedDifferences;
}
protected IDiffRelationshipComputer getDiffRelationshipComputer() {
return diffRelationshipComputer;
}
protected MergeMode getSelectedMode() {
if (isMirrored() && (selectedMode == LEFT_TO_RIGHT || selectedMode == RIGHT_TO_LEFT)) {
return selectedMode.inverse();
} else {
return selectedMode;
}
}
protected boolean isLeftEditable() {
return compareConfiguration.isLeftEditable();
}
protected boolean isRightEditable() {
return compareConfiguration.isRightEditable();
}
protected boolean isMirrored() {
return compareConfiguration.isMirrored();
}
}