org.eclipse.compare.structuremergeviewer.Differencer Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2000, 2013 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.compare.structuremergeviewer;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.compare.IStreamContentAccessor;
import org.eclipse.compare.ITypedElement;
import org.eclipse.compare.internal.MergeViewerContentProvider;
import org.eclipse.compare.internal.Utilities;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import com.ibm.icu.text.MessageFormat;
/**
* A generic two-way or three-way differencing engine.
*
* The engine is used by calling one of the findDifferences
methods and passing
* in the objects to compare.
* The engine calls the following methods on the input objects to perform the compare:
*
* getChildren
: for enumerating the children of an object (if any),
* contentsEqual
: for comparing the content of leaf objects, that is, objects without children,
* visit
: for every pair of compared object the compare result is passed in.
*
* Clients may use as is, or subclass to provide a custom implementation for the three hooks.
* However the default implementation already deals with the typical case:
*
* getChildren
: tries to apply the IStructureComparator
* interface to enumerate the children,
* contentsEqual
: tries to apply the IStreamContentAccessor
interface
* to perform a byte-wise content comparison,
* visit
: creates a DiffNode
for any detected difference between the compared objects and
* links it under a parent node effectively creating a tree of differences.
*
* The different kind of changes detected by the engine are decoded as follows:
* In the two-way case only NO_CHANGE, ADDITION, DELETION, and CHANGE are used.
* In the three-way case these constants are bitwise ORed with one of directional constants
* LEFT, RIGHT, and CONFLICTING.
*/
public class Differencer {
// The kind of differences.
/**
* Difference constant (value 0) indicating no difference.
*/
public static final int NO_CHANGE= 0;
/**
* Difference constant (value 1) indicating one side was added.
*/
public static final int ADDITION= 1;
/**
* Difference constant (value 2) indicating one side was removed.
*/
public static final int DELETION= 2;
/**
* Difference constant (value 3) indicating side changed.
*/
public static final int CHANGE= 3;
/**
* Bit mask (value 3) for extracting the kind of difference.
*/
public static final int CHANGE_TYPE_MASK= 3;
// The direction of a three-way change.
/**
* Three-way change constant (value 4) indicating a change on left side.
*/
public static final int LEFT= 4;
/**
* Three-way change constant (value 8) indicating a change on right side.
*/
public static final int RIGHT= 8;
/**
* Three-way change constant (value 12) indicating a change on left and
* right sides.
*/
public static final int CONFLICTING= 12;
/**
* Bit mask (value 12) for extracting the direction of a three-way change.
*/
public static final int DIRECTION_MASK= 12;
/**
* Constant (value 16) indicating a change on left and
* right side (with respect to ancestor) but left and right are identical.
*/
public static final int PSEUDO_CONFLICT= 16;
static class Node {
List fChildren;
int fCode;
Object fAncestor;
Object fLeft;
Object fRight;
Node() {
// nothing to do
}
Node(Node parent, Object ancestor, Object left, Object right) {
parent.add(this);
fAncestor= ancestor;
fLeft= left;
fRight= right;
}
void add(Node child) {
if (fChildren == null)
fChildren= new ArrayList<>();
fChildren.add(child);
}
Object visit(Differencer d, Object parent, int level) {
if (fCode == NO_CHANGE)
return null;
//dump(level);
Object data= d.visit(parent, fCode, fAncestor, fLeft, fRight);
if (fChildren != null) {
for (Node n : fChildren) {
n.visit(d, data, level + 1);
}
}
return data;
}
// private void dump(int level) {
// String name= null;
// if (fAncestor instanceof ITypedElement)
// name= ((ITypedElement)fAncestor).getName();
// if (name == null && fLeft instanceof ITypedElement)
// name= ((ITypedElement)fLeft).getName();
// if (name == null && fRight instanceof ITypedElement)
// name= ((ITypedElement)fRight).getName();
// if (name == null)
// name= "???"; //$NON-NLS-1$
//
// for (int i= 0; i < level; i++)
// System.out.print(" "); //$NON-NLS-1$
//
// System.out.println(getDiffType(fCode) + name);
// }
// private String getDiffType(int code) {
// String dir= " "; //$NON-NLS-1$
// switch (code & DIRECTION_MASK) {
// case LEFT:
// dir= ">"; //$NON-NLS-1$
// break;
// case RIGHT:
// dir= "<"; //$NON-NLS-1$
// break;
// case CONFLICTING:
// dir= "!"; //$NON-NLS-1$
// break;
// }
// String change= "="; //$NON-NLS-1$
// switch (code & CHANGE_TYPE_MASK) {
// case ADDITION:
// change= "+"; //$NON-NLS-1$
// break;
// case DELETION:
// change= "-"; //$NON-NLS-1$
// break;
// case CHANGE:
// change= "#"; //$NON-NLS-1$
// break;
// }
// return dir + change + " "; //$NON-NLS-1$
// }
}
/**
* Creates a new differencing engine.
*/
public Differencer() {
// nothing to do
}
/**
* Starts the differencing engine on the three input objects. If threeWay is true
a
* three-way comparison is performed, otherwise a two-way compare (in the latter case the ancestor argument is ignored).
* The progress monitor is passed to the method updateProgress
which is called for every node or
* leaf compare. The method returns the object that was returned from the top-most call to method visit
.
* At most two of the ancestor, left, and right parameters are allowed to be null
.
*
* @param threeWay if true
a three-way comparison is performed, otherwise a two-way compare
* @param pm a progress monitor which is passed to method updateProgress
* @param data a client data that is passed to the top-level call to visit
* @param ancestor the ancestor object of the compare (may be null
)
* @param left the left object of the compare
* @param right the right object of the compare
* @return the object returned from the top most call to method visit
,
* possibly null
*/
public Object findDifferences(boolean threeWay, IProgressMonitor pm, Object data, Object ancestor, Object left, Object right) {
Node root= new Node();
int code= traverse(threeWay, root, pm, threeWay ? ancestor : null, left, right);
if (code != NO_CHANGE) {
List l= root.fChildren;
if (!l.isEmpty()) {
Node first= l.get(0);
return first.visit(this, data, 0);
}
}
return null;
}
/*
* Traverse tree in postorder.
*/
private int traverse(boolean threeWay, Node parent, IProgressMonitor pm,
Object ancestor, Object left, Object right) {
Object[] ancestorChildren= getChildren(ancestor);
Object[] rightChildren= getChildren(right);
Object[] leftChildren= getChildren(left);
int code= NO_CHANGE;
Node node= new Node(parent, ancestor, left, right);
boolean content= true; // we reset this if we have at least one child
if (((threeWay && ancestorChildren != null) || !threeWay)
&& rightChildren != null && leftChildren != null) {
// we only recurse down if no leg is null
// a node
Set