Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 32eb38ea86be1f7daf4208f27bf5369c7e41655e (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
/*******************************************************************************
 * Copyright (c) 2006, 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.BufferedReader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.util.List;

import org.eclipse.compare.CompareUI;
import org.eclipse.compare.ICompareFilter;
import org.eclipse.compare.IEditableContent;
import org.eclipse.compare.IEncodedStreamContentAccessor;
import org.eclipse.compare.ISharedDocumentAdapter;
import org.eclipse.compare.IStreamContentAccessor;
import org.eclipse.compare.ITypedElement;
import org.eclipse.compare.SharedDocumentAdapter;
import org.eclipse.compare.contentmergeviewer.IDocumentRange;
import org.eclipse.compare.internal.CompareUIPlugin;
import org.eclipse.compare.internal.Utilities;
import org.eclipse.compare.internal.patch.LineReader;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension3;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.services.IDisposable;
import org.eclipse.ui.texteditor.IDocumentProvider;

/**
 * An {@link IStructureCreator2} that attempts to use an {@link IDocumentProvider}
 * to obtain a shared document for an {@link ITypedElement}.
 * <p>
 * Clients may subclass this class.
 * </p>
 *
 * @since 3.3
 */
public abstract class StructureCreator implements IStructureCreator2 {
	@Override
	public IStructureComparator getStructure(Object input) {
		String contents= null;
		IDocument doc= CompareUI.getDocument(input);
		if (doc == null) {
			if (input instanceof IStreamContentAccessor) {
				IStreamContentAccessor sca= (IStreamContentAccessor) input;
				try {
					contents= Utilities.readString(sca);
				} catch (CoreException e) {
					// return null indicates the error.
					CompareUIPlugin.log(e);
					return null;
				}
			}

			if (contents == null) {
				// Node has no contents
				return null;
			}

			doc= new Document(contents);
			setupDocument(doc);
		}

		try {
			return createStructureComparator(input, doc, null, null);
		} catch (CoreException e) {
			CompareUIPlugin.log(e);
			return null;
		}
	}

	@Override
	public IStructureComparator createStructure(final Object element,
			final IProgressMonitor monitor) throws CoreException {
		final IStructureComparator[] result = new IStructureComparator[] { null };
		Runnable runnable = new Runnable() {
			@Override
			public void run() {
				try {
					result[0]= internalCreateStructure(element, monitor);
				} catch (OperationCanceledException ex) {
					return;
				}
			}
		};
		Utilities.runInUIThread(runnable);
		return result[0];
	}

	/*
	 * We need to create the structure in the UI thread since IDocument requires this
	 */
	private IStructureComparator internalCreateStructure(Object element,
			IProgressMonitor monitor) {
		final ISharedDocumentAdapter sda = SharedDocumentAdapterWrapper.getAdapter(element);
		if (sda != null) {
			final IEditorInput input = sda.getDocumentKey(element);
			if (input != null) {
				final IDocumentProvider provider = SharedDocumentAdapter.getDocumentProvider(input);
				if (provider != null) {
					try {
						sda.connect(provider, input);
						IDocument document = provider.getDocument(input);
						setupDocument(document);
						return createStructureComparator(element, document, wrapSharedDocumentAdapter(sda, element, document), monitor);
					} catch (CoreException e) {
						// Connection to the document provider failed.
						// Log and fall through to use simple structure
						CompareUIPlugin.log(e);
					}
				}
			}
		}
		return getStructure(element);
	}

	/**
	 * Creates an {@link IStructureComparator} for the given element using the
	 * contents available in the given document. If the provided
	 * {@link ISharedDocumentAdapter} is not <code>null</code> then the
	 * {@link IStructureComparator} returned by this method must implement the
	 * {@link IDisposable} interface and disconnect from the adapter when the
	 * comparator is disposed. The {@link StructureDiffViewer} class will call
	 * dispose if the {@link IStructureComparator} also implements
	 * {@link IDisposable}. Other clients must do the same.
	 * <p>
	 * It should be noted that the provided {@link ISharedDocumentAdapter}
	 * will provide the key associated with the given element when
	 * {@link ISharedDocumentAdapter#getDocumentKey(Object)} is called
	 * for any {@link IDocumentRange} node whose document matches the
	 * provided document. Thus, this adapter should also be returned
	 * by the structure comparator and its children when they are adapted
	 * to an {@link ISharedDocumentAdapter}.
	 * @param element the element
	 * @param document the document that has the contents for the element
	 * @param sharedDocumentAdapter the shared document adapter from which the
	 *            document was obtained or <code>null</code> if the document
	 *            is not shared.
	 * @param monitor a progress monitor or <code>null</code> if progress is not required
	 *
	 * @return a structure comparator
	 * @throws CoreException
	 */
	protected abstract IStructureComparator createStructureComparator(
			final Object element, IDocument document,
			final ISharedDocumentAdapter sharedDocumentAdapter,
			IProgressMonitor monitor) throws CoreException;

	/**
	 * Sets up the newly created document as appropriate. Any document partitioners
	 * should be added to a custom slot using the {@link IDocumentExtension3} interface
	 * in case the document is shared via a file buffer.
	 * @param document a document
	 */
	protected void setupDocument(IDocument document) {
		String partitioning = getDocumentPartitioning();
		if (partitioning == null || !(document instanceof IDocumentExtension3)) {
			if (document.getDocumentPartitioner() == null) {
				IDocumentPartitioner partitioner= getDocumentPartitioner();
				if (partitioner != null) {
					document.setDocumentPartitioner(partitioner);
					partitioner.connect(document);
				}
			}
		} else {
			IDocumentExtension3 ex3 = (IDocumentExtension3) document;
			if (ex3.getDocumentPartitioner(partitioning) == null) {
				IDocumentPartitioner partitioner= getDocumentPartitioner();
				if (partitioner != null) {
					ex3.setDocumentPartitioner(partitioning, partitioner);
					partitioner.connect(document);
				}
			}
		}
	}

	/**
	 * Returns the partitioner to be associated with the document or
	 * <code>null</code> is partitioning is not needed or if the subclass
	 * overrode {@link #setupDocument(IDocument)} directly.
	 * @return a partitioner
	 */
	protected IDocumentPartitioner getDocumentPartitioner() {
		return null;
	}

	/**
	 * Returns the partitioning to which the partitioner returned from
	 * {@link #getDocumentPartitioner()} is to be associated. Return <code>null</code>
	 * only if partitioning is not needed or if the subclass
	 * overrode {@link #setupDocument(IDocument)} directly.
	 * @see IDocumentExtension3
	 * @return a partitioning
	 */
	protected String getDocumentPartitioning() {
		return null;
	}

	/**
	 * Default implementation of save that extracts the contents from
	 * the document of an {@link IDocumentRange} and sets it on the
	 * input. If the input is an {@link IEncodedStreamContentAccessor},
	 * the charset of the input is used to extract the contents from the
	 * document. If the input adapts to {@link ISharedDocumentAdapter} and
	 * the document of the {@link IDocumentRange} matches that of the
	 * input, then the save is issued through the shared document adapter.
	 * @see org.eclipse.compare.structuremergeviewer.IStructureCreator#save(org.eclipse.compare.structuremergeviewer.IStructureComparator, java.lang.Object)
	 */
	@Override
	public void save(IStructureComparator node, Object input) {
		if (node instanceof IDocumentRange && input instanceof IEditableContent) {
			IDocument document= ((IDocumentRange)node).getDocument();
			// First check to see if we have a shared document
			final ISharedDocumentAdapter sda = SharedDocumentAdapterWrapper.getAdapter(input);
			if (sda != null) {
				IEditorInput key = sda.getDocumentKey(input);
				if (key != null) {
					IDocumentProvider provider = SharedDocumentAdapter.getDocumentProvider(key);
					if (provider != null) {
						IDocument providerDoc = provider.getDocument(key);
						// We have to make sure that the document we are saving is the same as the shared document
						if (providerDoc != null && providerDoc == document) {
							if (save(provider, document, input, sda, key))
								return;
						}
					}
				}
			}
			IEditableContent bca= (IEditableContent) input;
			String contents= document.get();
			String encoding= null;
			if (input instanceof IEncodedStreamContentAccessor) {
				try {
					encoding= ((IEncodedStreamContentAccessor)input).getCharset();
				} catch (CoreException e1) {
					// ignore
				}
			}
			if (encoding == null)
				encoding= ResourcesPlugin.getEncoding();
			byte[] bytes;
			try {
				bytes= contents.getBytes(encoding);
			} catch (UnsupportedEncodingException e) {
				bytes= contents.getBytes();
			}
			bca.setContent(bytes);
		}
	}

	private boolean save(final IDocumentProvider provider, final IDocument document,
			final Object input, final ISharedDocumentAdapter sda, final IEditorInput key) {
		try {
			sda.flushDocument(provider, key, document, false);
			return true;
		} catch (CoreException e) {
			CompareUIPlugin.log(e);
		}
		return false;
	}

	/**
	 * Create an {@link ISharedDocumentAdapter} that will provide the document key for the given input
	 * object for any {@link DocumentRangeNode} instances whose document is the same as the
	 * provided document.
	 * @param input the input element
	 * @param document the document associated with the input element
	 * @return a shared document adapter that provides the proper document key for document range nodes
	 */
	private final ISharedDocumentAdapter wrapSharedDocumentAdapter(ISharedDocumentAdapter elementAdapter, final Object input, final IDocument document) {
		// We need to wrap the adapter so that the proper document key gets returned
		return new SharedDocumentAdapterWrapper(elementAdapter) {
			@Override
			public IEditorInput getDocumentKey(Object element) {
				if (hasSameDocument(element)) {
					return super.getDocumentKey(input);
				}
				return super.getDocumentKey(element);
			}
			private boolean hasSameDocument(Object element) {
				if (element instanceof DocumentRangeNode) {
					DocumentRangeNode drn = (DocumentRangeNode) element;
					return drn.getDocument() == document;
				}
				return false;
			}
		};
	}

	/**
	 * Default implementation of {@link #createElement(Object, Object, IProgressMonitor)}
	 * that uses {@link #getPath(Object, Object)} to determine the
	 * path for the element, {@link #createStructure(Object, IProgressMonitor)} to create the structure
	 * and {@link #findElement(IStructureComparator, String[])} to find the
	 * element in the structure. Subclasses may override.
	 * @param element the element
	 * @param input the containing input
	 * @param monitor a progress monitor
	 * @return the sub-structure element in the input for the given element
	 * @throws CoreException if a parse error occurred
	 */
	@Override
	public ITypedElement createElement(Object element, Object input, IProgressMonitor monitor)
			throws CoreException {
		String[] path= getPath(element, input);
		if (path == null) {
			// TODO: Temporary code until subclasses are updated
			IStructureComparator locate = locate(element, input);
			if (locate instanceof ITypedElement) {
				return (ITypedElement)locate;
			}
			return null;
		}

		// Build the structure
		IStructureComparator structure= createStructure(input, monitor);
		if (structure == null)	// we couldn't parse the structure
			return null;		// so we can't find anything

		// find the path in the tree
		return findElement(structure, path);
	}

	/**
	 * Default implementation of {@link #locate(Object, Object)} that
	 * uses {@link #getPath(Object, Object)} to determine the
	 * path for the element, {@link #getStructure(Object)} to create the structure
	 * and {@link #findElement(IStructureComparator, String[])} to find the
	 * element in the structure.  Subclasses may override.
	 * @param element the element
	 * @param input the containing input
	 * @return the sub-structure element in the input for the given element
	 */
	@Override
	public IStructureComparator locate(Object element, Object input) {
		String[] path= getPath(element, input);
		if (path == null)
			return null;
		// Build the structure
		IStructureComparator structure= getStructure(input);
		if (structure == null)	// we couldn't parse the structure
			return null;		// so we can't find anything

		// find the path in the tree
		return (IStructureComparator)findElement(structure, path);
	}

	/**
	 * Finds the element at the given path in the given structure.
	 * This method is invoked from the {@link #createElement(Object, Object, IProgressMonitor)}
	 * and {@link #locate(Object, Object)} methods to find the element for
	 * the given path.
	 * @param structure the structure
	 * @param path the path of an element in the structure
	 * @return the element at the given path in the structure or <code>null</code>
	 */
	protected ITypedElement findElement(IStructureComparator structure, String[] path) {
		return (ITypedElement)find(structure, path, 0);
	}

	/**
	 * Recursively extracts the given path from the tree.
	 */
	private IStructureComparator find(IStructureComparator tree, String[] path, int index) {
		if (tree != null) {
			Object[] children= tree.getChildren();
			if (children != null) {
				for (int i= 0; i < children.length; i++) {
					IStructureComparator child= (IStructureComparator) children[i];
					if (child instanceof ITypedElement && child instanceof DocumentRangeNode) {
						String n1= null;
						if (child instanceof DocumentRangeNode)
							n1= ((DocumentRangeNode)child).getId();
						if (n1 == null)
							n1= ((ITypedElement)child).getName();
						String n2= path[index];
						if (n1.equals(n2)) {
							if (index == path.length-1)
								return child;
							IStructureComparator result= find(child, path, index+1);
							if (result != null)
								return result;
						}
					}
				}
			}
		}
		return null;
	}

	/**
	 * Returns the path of the element in the structure of it's containing input
	 * or <code>null</code> if the element is not contained in the input. This method is
	 * invoked from {@link #createElement(Object, Object, IProgressMonitor)} and
	 * {@link #locate(Object, Object)} methods to determine
	 * the path to be passed to {@link #findElement(IStructureComparator, String[])}.
	 * By default, <code>null</code> is returned. Subclasses may override.
	 * @param element the element
	 * @param input the input
	 * @return the path of the element in the structure of it's containing input
	 * or <code>null</code>
	 */
	protected String[] getPath(Object element, Object input) {
		return null;
	}

	@Override
	public void destroy(Object object) {
		IDisposable disposable = getDisposable(object);
		if (disposable != null)
			disposable.dispose();
	}

	private IDisposable getDisposable(Object object) {
		if (object instanceof IDisposable) {
			return (IDisposable) object;
		}
		if (object instanceof DocumentRangeNode) {
			DocumentRangeNode node = (DocumentRangeNode) object;
			return getDisposable(node.getParentNode());
		}
		return null;
	}

	/**
	 * Returns true if the two nodes are equal for comparison purposes. If
	 * <code>compareFilters</code> is not empty, the filters are applied to each
	 * line of each node's text representation.
	 *
	 * @param node1
	 * @param contributor1
	 *            either 'A', 'L', or 'R' for ancestor, left or right
	 *            contributor
	 * @param node2
	 * @param contributor2
	 *            either 'A', 'L', or 'R' for ancestor, left or right
	 *            contributor
	 * @param ignoreWhitespace
	 *            if <code>true</code> whitespace characters will be ignored
	 *            when determining equality. Note: Will bypass any custom ignore
	 *            whitespace behaviors contributed through implementations of
	 *            <code>org.eclipse.compare.structuremergeviewer.IStructureCreator.getContents()</code>
	 * @param compareFilters
	 *            the filters used to customize the comparison of lines of text.
	 * @return whether the two nodes are equal for comparison purposes
	 * @noreference This method is not intended to be referenced by clients.
	 * @since 3.6
	 */
	public boolean contentsEquals(Object node1, char contributor1,
			Object node2, char contributor2, boolean ignoreWhitespace,
			ICompareFilter[] compareFilters) {

		List<String> lines1 = LineReader.readLines(new BufferedReader(new StringReader(
				getContents(node1, false))));
		List<String> lines2 = LineReader.readLines(new BufferedReader(new StringReader(
				getContents(node2, false))));

		StringBuilder buffer1 = new StringBuilder();
		StringBuilder buffer2 = new StringBuilder();

		int maxLines = Math.max(lines1.size(), lines2.size());
		for (int i = 0; i < maxLines; i++) {
			String s1 = lines1.size() > i ? (String) lines1.get(i) : ""; //$NON-NLS-1$
			String s2 = lines2.size() > i ? (String) lines2.get(i) : ""; //$NON-NLS-1$

			if (compareFilters != null && compareFilters.length > 0) {
				s1 = Utilities.applyCompareFilters(s1, contributor1, s2,
						contributor2, compareFilters);
				s2 = Utilities.applyCompareFilters(s2, contributor2, s1,
						contributor1, compareFilters);
			}
			buffer1.append(s1);
			buffer2.append(s2);
		}
		if (ignoreWhitespace) {
			int l1 = buffer1.length();
			int l2 = buffer2.length();
			int c1 = 0, c2 = 0;
			int i1 = 0, i2 = 0;

			while (c1 != -1) {

				c1 = -1;
				while (i1 < l1) {
					char c = buffer1.charAt(i1++);
					if (!Character.isWhitespace(c)) {
						c1 = c;
						break;
					}
				}

				c2 = -1;
				while (i2 < l2) {
					char c = buffer2.charAt(i2++);
					if (!Character.isWhitespace(c)) {
						c2 = c;
						break;
					}
				}

				if (c1 != c2)
					return false;
			}
		} else if (!buffer1.toString().equals(buffer2.toString())) {
			return false;
		}

		return true;
	}
}

Back to the top