Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 182790e3ec053ee6e0daaca83a068a8d13ed2dde (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
/*******************************************************************************
 * 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.Iterator;
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.
 * <p>
 * The engine is used by calling one of the <code>findDifferences</code> methods and passing
 * in the objects to compare.
 * The engine calls the following methods on the input objects to perform the compare:
 * <UL>
 * <LI><code>getChildren</code>: for enumerating the children of an object (if any),
 * <LI><code>contentsEqual</code>: for comparing the content of leaf objects, that is, objects without children,
 * <LI><code>visit</code>: for every pair of compared object the compare result is passed in.
 * </UL>
 * 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:
 * <UL>
 * <LI><code>getChildren</code>: tries to apply the <code>IStructureComparator</code>
 * 	interface to enumerate the children,
 * <LI><code>contentsEqual</code>: tries to apply the <code>IStreamContentAccessor</code> interface
 *	to perform a byte-wise content comparison,
 * <LI><code>visit</code>: creates a <code>DiffNode</code> for any detected difference between the compared objects and
 *	links it under a parent node effectively creating a tree of differences.
 * </UL>
 * 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) {
				Iterator i= fChildren.iterator();
				while (i.hasNext()) {
					Node n= (Node) i.next();
					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 <code>true</code> 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 <code>updateProgress</code> which is called for every node or
	 * leaf compare. The method returns the object that was returned from the top-most call to method <code>visit</code>.
	 * At most two of the ancestor, left, and right parameters are allowed to be <code>null</code>.
	 *
	 * @param threeWay if <code>true</code> a three-way comparison is performed, otherwise a two-way compare
	 * @param pm a progress monitor which is passed to method <code>updateProgress</code>
	 * @param data a client data that is passed to the top-level call to <code>visit</code>
	 * @param ancestor the ancestor object of the compare (may be <code>null</code>)
	 * @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 <code>visit</code>,
	 *   possibly <code>null</code>
	 */
	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.size() > 0) {
				Node first= (Node)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 allSet= new HashSet(20);
			Map ancestorSet= null;
			Map rightSet= null;
			Map leftSet= null;
						
			if (ancestorChildren != null) {
				ancestorSet= new HashMap(10);
				for (int i= 0; i < ancestorChildren.length; i++) {
					Object ancestorChild= ancestorChildren[i];
					ancestorSet.put(ancestorChild, ancestorChild);
					allSet.add(ancestorChild);
				}
			}

			if (rightChildren != null) {
				rightSet= new HashMap(10);
				for (int i= 0; i < rightChildren.length; i++) {
					Object rightChild= rightChildren[i];
					rightSet.put(rightChild, rightChild);
					allSet.add(rightChild);
				}
			}

			if (leftChildren != null) {
				leftSet= new HashMap(10);
				for (int i= 0; i < leftChildren.length; i++) {
					Object leftChild= leftChildren[i];
					leftSet.put(leftChild, leftChild);
					allSet.add(leftChild);
				}
			}
						
			Iterator e= allSet.iterator();
			while (e.hasNext()) {
				Object keyChild= e.next();
				
				if (pm != null) {
					
					if (pm.isCanceled())
						throw new OperationCanceledException();
						
					updateProgress(pm, keyChild);
				}
				
				Object ancestorChild= ancestorSet != null ? ancestorSet.get(keyChild) : null;
				Object leftChild= leftSet != null ? leftSet.get(keyChild) : null;
				Object rightChild= rightSet != null ? rightSet.get(keyChild) : null;
				
				int c= traverse(threeWay, node, pm, ancestorChild, leftChild, rightChild);
			
				if ((c & CHANGE_TYPE_MASK) != NO_CHANGE) {
					code|= CHANGE;	// deletions and additions of child result in a change of the container
					code|= (c & DIRECTION_MASK);	// incoming & outgoing are just ored
					content= false;
				}
			}
		}

		if (content)			// a leaf
			code= compare(threeWay, ancestor, left, right);
								
		node.fCode= code;
							
		return code;
	}
	
	/**
	 * Called for every node or leaf comparison.
	 * The differencing engine passes in the input objects of the compare and the result of the compare.
	 * The data object is the value returned from a call to the <code>visit</code> method on the parent input.
	 * It can be considered the "parent" reference and is useful when building a tree.
	 * <p>
	 * The <code>Differencer</code> implementation returns a new
	 * <code>DiffNode</code> which is initialized with the corresponding values.
	 * Subclasses may override.
	 *
	 * @param data object returned from parent call to <code>visit</code>,
	 *   possibly <code>null</code>
	 * @param result the result of the compare operation performed on the three inputs
	 * @param ancestor the compare ancestor of the left and right inputs
	 * @param left the left input to the compare
	 * @param right the right input to the compare
	 * @return the result, possibly <code>null</code>
	 */
	protected Object visit(Object data, int result, Object ancestor, Object left, Object right) {
		return new DiffNode((IDiffContainer) data, result, (ITypedElement)ancestor, (ITypedElement)left, (ITypedElement)right);
	}
	
	/*
	 * Performs a 2-way or 3-way compare of the given leaf elements and returns an integer
	 * describing the kind of difference.
	 */
	private int compare(boolean threeway, Object ancestor, Object left, Object right) {
		
		int description= NO_CHANGE;
		
		if (threeway) {
			if (ancestor == null) {
				if (left == null) {
					if (right == null) {
						Assert.isTrue(false);
						// shouldn't happen
					} else {
						description= RIGHT | ADDITION;
					}
				} else {
					if (right == null) {
						description= LEFT | ADDITION;
					} else {
						description= CONFLICTING | ADDITION;
						if (contentsEqual(left, MergeViewerContentProvider.LEFT_CONTRIBUTOR, right, MergeViewerContentProvider.RIGHT_CONTRIBUTOR))
							description|= PSEUDO_CONFLICT;
					}
				}
			} else {
				if (left == null) {
					if (right == null) {
						description= CONFLICTING | DELETION | PSEUDO_CONFLICT;
					} else {
						if (contentsEqual(ancestor,	MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR, right, MergeViewerContentProvider.RIGHT_CONTRIBUTOR))
							description= LEFT | DELETION;
						else
							description= CONFLICTING | CHANGE;	
					}
				} else {
					if (right == null) {
						if (contentsEqual(ancestor, MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR, left, MergeViewerContentProvider.LEFT_CONTRIBUTOR))
							description= RIGHT | DELETION;
						else
							description= CONFLICTING | CHANGE;	
					} else {
						boolean ay= contentsEqual(ancestor, MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR, left, MergeViewerContentProvider.LEFT_CONTRIBUTOR);
						boolean am= contentsEqual(ancestor, MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR, right, MergeViewerContentProvider.RIGHT_CONTRIBUTOR);
						
						if (ay && am) {
							// empty
						} else if (ay && !am) {
							description= RIGHT | CHANGE;
						} else if (!ay && am) {
							description= LEFT | CHANGE;
						} else {
							description= CONFLICTING | CHANGE;
							if (contentsEqual(left, MergeViewerContentProvider.LEFT_CONTRIBUTOR, right, MergeViewerContentProvider.RIGHT_CONTRIBUTOR))
								description|= PSEUDO_CONFLICT;
						}
					}
				}
			}
		} else {	// two way compare ignores ancestor
			if (left == null) {
				if (right == null) {
					Assert.isTrue(false);
					// shouldn't happen
				} else {
					description= ADDITION;
				}
			} else {
				if (right == null) {
					description= DELETION;
				} else {
					if (! contentsEqual(left, MergeViewerContentProvider.LEFT_CONTRIBUTOR, right, MergeViewerContentProvider.RIGHT_CONTRIBUTOR))
						description= CHANGE;
				}
			}
		}
							
		return description;
	}

	/**
	 * Performs a content compare on the two given inputs.
	 * <p>
	 * The <code>Differencer</code> implementation returns
	 * <code>contentsEqual(Object input1, Object input2)</code>. Subclasses may
	 * override to implement a different content compare on the given inputs.
	 * </p>
	 * 
	 * @param input1
	 *            first input to contents compare
	 * @param contributor1
	 *            the contributor of input1
	 * @param input2
	 *            second input to contents compare
	 * @param contributor2
	 *            the contributor of input2
	 * @return <code>true</code> if content is equal
	 * @noreference This method is not intended to be referenced by clients.
	 * @since 3.6
	 */
	protected boolean contentsEqual(Object input1, char contributor1,
			Object input2, char contributor2) {
		return contentsEqual(input1, input2);
	}

	/**
	 * Performs a content compare on the two given inputs.
	 * <p>
	 * The <code>Differencer</code> implementation
	 * returns <code>true</code> if both inputs implement <code>IStreamContentAccessor</code>
	 * and their byte contents is identical. Subclasses may override to implement 
	 * a different content compare on the given inputs.
	 * </p>
	 *
	 * @param input1 first input to contents compare
	 * @param input2 second input to contents compare
	 * @return <code>true</code> if content is equal
	 */
	protected boolean contentsEqual(Object input1, Object input2) {
		
		if (input1 == input2)
			return true;
			
		InputStream is1= getStream(input1);
		InputStream is2= getStream(input2);
		
		if (is1 == null && is2 == null)	// no byte contents
			return true;
		
		try {
			if (is1 == null || is2 == null)	// only one has contents
				return false;
			
			while (true) {
				int c1= is1.read();
				int c2= is2.read();
				if (c1 == -1 && c2 == -1)
					return true;
				if (c1 != c2)
					break;
				
			}
		} catch (IOException ex) {
			// NeedWork
		} finally {
			if (is1 != null) {
				try {
					is1.close();
				} catch(IOException ex) {
					// silently ignored
				}
			}
			if (is2 != null) {
				try {
					is2.close();
				} catch(IOException ex) {
					// silently ignored
				}
			}
		}
		return false;
	}
	
	/*
	 * Tries to return an InputStream for the given object.
	 * Returns <code>null</code> if the object not an IStreamContentAccessor
	 * or an error occurred.
	 */
	private InputStream getStream(Object o) {
		if (o instanceof IStreamContentAccessor) {
			try {
				return ((IStreamContentAccessor)o).getContents();
			} catch(CoreException ex) {
				// NeedWork
			}
		}
		return null;
	}
	
	/**
	 * Returns the children of the given input or <code>null</code> if there are no children.
	 * <p>
	 * The <code>Differencer</code> implementation checks whether the input 
	 * implements the <code>IStructureComparator</code> interface. If yes it is used
	 * to return an array containing all children. Otherwise <code>null</code> is returned.
	 * Subclasses may override to implement a different strategy to enumerate children.
	 * </p>
	 *
	 * @param input the object for which to return children
	 * @return the children of the given input or <code>null</code> if there are no children.
	 */
	protected Object[] getChildren(Object input) {
		if (input instanceof IStructureComparator)
			return ((IStructureComparator)input).getChildren();
		return null;
	}
	
	/**
	 * Called for every leaf or node compare to update progress information.
	 * <p>
	 * The <code>Differencer</code> implementation shows the name of the input object
	 * as a subtask. Subclasses may override.
	 * </p>
	 *
	 * @param progressMonitor the progress monitor for reporting progress
	 * @param node the currently processed non-<code>null</code> node
	 */
	protected void updateProgress(IProgressMonitor progressMonitor, Object node) {
		if (node instanceof ITypedElement) {
			String name= ((ITypedElement)node).getName();
			String fmt= Utilities.getString("Differencer.progressFormat"); //$NON-NLS-1$
			String msg= MessageFormat.format(fmt, name );
			progressMonitor.subTask(msg);
			//progressMonitor.worked(1);
		}
	}
}

Back to the top