All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.compare.structuremergeviewer.StructureCreator Maven / Gradle / Ivy

There is a newer version: 3.11.0
Show newest version
/*******************************************************************************
 * 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}.
 * 

* Clients may subclass this class. *

* * @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 null 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. *

* 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 null if the document * is not shared. * @param monitor a progress monitor or null 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 * null 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 null * 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 null */ 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 null 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, null 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 null */ 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 * compareFilters 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 true whitespace characters will be ignored * when determining equality. Note: Will bypass any custom ignore * whitespace behaviors contributed through implementations of * org.eclipse.compare.structuremergeviewer.IStructureCreator.getContents() * @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 lines1 = LineReader.readLines(new BufferedReader(new StringReader( getContents(node1, false)))); List lines2 = LineReader.readLines(new BufferedReader(new StringReader( getContents(node2, false)))); StringBuffer buffer1 = new StringBuffer(); StringBuffer buffer2 = new StringBuffer(); 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy