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

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

There is a newer version: 3.11.0
Show newest version
/*******************************************************************************
 * Copyright (c) 2000, 2011 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.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;

import org.eclipse.compare.IEditableContent;
import org.eclipse.compare.IEditableContentExtension;
import org.eclipse.compare.IEncodedStreamContentAccessor;
import org.eclipse.compare.ISharedDocumentAdapter;
import org.eclipse.compare.IStreamContentAccessor;
import org.eclipse.compare.ITypedElement;
import org.eclipse.compare.contentmergeviewer.IDocumentRange;
import org.eclipse.compare.internal.CompareUIPlugin;
import org.eclipse.compare.internal.Utilities;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.Position;
import org.eclipse.swt.widgets.Shell;


/**
 * A document range node represents a structural element
 * when performing a structure compare of documents.
 * DocumentRangeNodes are created while parsing the document and represent
 * a semantic entity (e.g. a Java class or method).
 * As a consequence of the parsing a DocumentRangeNode maps to a range
 * of characters in the document.
 * 

* Since a DocumentRangeNode implements the IStructureComparator * and IStreamContentAccessor interfaces it can be used as input to the * differencing engine. This makes it possible to perform * a structural diff on a document and have the nodes and leaves of the compare easily map * to character ranges within the document. *

* Clients need to be aware that this node registers position updaters with the document * using {@link IDocument#addPosition(String, Position)} with the category set to * {@link IDocumentRange#RANGE_CATEGORY}. The {@link StructureDiffViewer} will * remove the category when the nodes are no longer being used. Other clients * must do the same. *

* Subclasses may add additional state collected while parsing the document. *

* @see Differencer */ public class DocumentRangeNode implements IDocumentRange, IStructureComparator, IEditableContent, IEncodedStreamContentAccessor, IAdaptable, IEditableContentExtension { private static final String UTF_16= "UTF-16"; //$NON-NLS-1$ private IDocument fBaseDocument; private Position fRange; // the range in the base document private int fTypeCode; private String fID; private Position fAppendPosition; // a position where to insert a child textually private ArrayList fChildren; private final DocumentRangeNode fParent; /** * Creates a new DocumentRangeNode for the given range within the specified * document. The typeCode is uninterpreted client data. The ID is used when comparing * two nodes with each other: i.e. the differencing engine performs a content compare * on two nodes if their IDs are equal. * * @param typeCode a type code for this node * @param id an identifier for this node * @param document document on which this node is based on * @param start start position of range within document * @param length length of range */ public DocumentRangeNode(int typeCode, String id, IDocument document, int start, int length) { this(null, typeCode, id, document, start, length); } /** * Creates a new DocumentRangeNode for the given range within the specified * document. The typeCode is uninterpreted client data. The ID is used when comparing * two nodes with each other: i.e. the differencing engine performs a content compare * on two nodes if their IDs are equal. * * @param parent the parent node * @param typeCode a type code for this node * @param id an identifier for this node * @param document document on which this node is based on * @param start start position of range within document * @param length length of range * @since 3.3 */ public DocumentRangeNode(DocumentRangeNode parent, int typeCode, String id, IDocument document, int start, int length) { fParent = parent; fTypeCode= typeCode; fID= id; fBaseDocument= document; registerPositionUpdater(start, length); } private void registerPositionUpdater(int start, int length) { fBaseDocument.addPositionCategory(RANGE_CATEGORY); fRange= new Position(start, length); try { fBaseDocument.addPosition(RANGE_CATEGORY, fRange); } catch (BadPositionCategoryException ex) { CompareUIPlugin.log(ex); } catch (BadLocationException ex) { CompareUIPlugin.log(ex); } } /* (non Javadoc) * see IDocumentRange.getDocument */ @Override public IDocument getDocument() { return fBaseDocument; } /* (non Javadoc) * see IDocumentRange.getRange */ @Override public Position getRange() { return fRange; } /** * Returns the type code of this node. * The type code is uninterpreted client data which can be set in the constructor. * * @return the type code of this node */ public int getTypeCode() { return fTypeCode; } /** * Returns this node's id. * It is used in equals and hashcode. * * @return the node's id */ public String getId() { return fID; } /** * Sets this node's id. * It is used in equals and hashcode. * * @param id the new id for this node */ public void setId(String id) { fID= id; } /** * Adds the given node as a child. * * @param node the node to add as a child */ public void addChild(DocumentRangeNode node) { if (fChildren == null) fChildren= new ArrayList<>(); fChildren.add(node); } @Override public Object[] getChildren() { if (fChildren != null) return fChildren.toArray(); return new Object[0]; } /** * Sets the length of the range of this node. * * @param length the length of the range */ public void setLength(int length) { getRange().setLength(length); } /** * Sets a position within the document range that can be used to (legally) insert * text without breaking the syntax of the document. *

* E.g. when parsing a Java document the "append position" of a DocumentRangeNode * representing a Java class could be the character position just before the closing bracket. * Inserting the text of a new method there would not disturb the syntax of the class. * * @param pos the character position within the underlying document where text can be legally inserted */ public void setAppendPosition(int pos) { if (fAppendPosition != null) try { fBaseDocument.removePosition(RANGE_CATEGORY, fAppendPosition); } catch (BadPositionCategoryException e) { // Ignore } try { // TODO: Avoid an exception for a position that is past the end of the document if (pos <= getDocument().getLength()) { Position p= new Position(pos); fBaseDocument.addPosition(RANGE_CATEGORY, p); fAppendPosition= p; } } catch (BadPositionCategoryException ex) { // silently ignored } catch (BadLocationException ex) { // silently ignored } } /** * Returns the position that has been set with setAppendPosition. * If setAppendPosition hasn't been called, the position after the last character * of this range is returned. This method will return null if the position * could not be registered with the document. * * @return a position where text can be legally inserted */ public Position getAppendPosition() { if (fAppendPosition == null) { try { Position p= new Position(fBaseDocument.getLength()); fBaseDocument.addPosition(RANGE_CATEGORY, p); fAppendPosition= p; return fAppendPosition; } catch (BadPositionCategoryException ex) { // silently ignored } catch (BadLocationException ex) { // silently ignored } } return new Position(fBaseDocument.getLength()); } /** * Implementation based on getID. * @param other the object to compare this DocumentRangeNode against. * @return true if the DocumentRangeNodesare equal; false otherwise. */ @Override public boolean equals(Object other) { if (other != null && other.getClass() == getClass()) { DocumentRangeNode tn= (DocumentRangeNode) other; return fTypeCode == tn.fTypeCode && fID.equals(tn.fID); } return super.equals(other); } /** * Implementation based on getID. * @return a hash code for this object. */ @Override public int hashCode() { return fID.hashCode(); } /* * Find corresponding position */ private Position findCorrespondingPosition(DocumentRangeNode otherParent, DocumentRangeNode child) { // we try to find a predecessor of left Node which exists on the right side if (child != null && fChildren != null) { int ix= otherParent.fChildren.indexOf(child); if (ix >= 0) { for (int i= ix - 1; i >= 0; i--) { DocumentRangeNode c1= otherParent.fChildren.get(i); int i2= fChildren.indexOf(c1); if (i2 >= 0) { DocumentRangeNode c= fChildren.get(i2); //System.out.println(" found corresponding: " + i2 + " " + c); Position p= c.fRange; //try { Position po= new Position(p.getOffset() + p.getLength() + 1, 0); //c.fBaseDocument.addPosition(RANGE_CATEGORY, po); return po; //} catch (BadLocationException ex) { //} //break; } } for (int i= ix; i < otherParent.fChildren.size(); i++) { DocumentRangeNode c1= otherParent.fChildren.get(i); int i2= fChildren.indexOf(c1); if (i2 >= 0) { DocumentRangeNode c= fChildren.get(i2); //System.out.println(" found corresponding: " + i2 + " " + c); Position p= c.fRange; //try { Position po= new Position(p.getOffset(), 0); //c.fBaseDocument.addPosition(RANGE_CATEGORY, po); return po; //} catch (BadLocationException ex) { //} //break; } } } } return getAppendPosition(); } private void add(String s, DocumentRangeNode parent, DocumentRangeNode child) { Position p= findCorrespondingPosition(parent, child); if (p != null) { try { fBaseDocument.replace(p.getOffset(), p.getLength(), s); } catch (BadLocationException ex) { CompareUIPlugin.log(ex); } } } /* (non Javadoc) * see IStreamContentAccessor.getContents */ @Override public InputStream getContents() { String s; try { s= fBaseDocument.get(fRange.getOffset(), fRange.getLength()); } catch (BadLocationException ex) { s= ""; //$NON-NLS-1$ } return new ByteArrayInputStream(Utilities.getBytes(s, UTF_16)); } /** * If this node has a parent, return the editability of the parent. * Otherwise return true. Subclasses may override. * @see org.eclipse.compare.IEditableContent#isEditable() */ @Override public boolean isEditable() { if (fParent != null) return fParent.isEditable(); return true; } @Override public ITypedElement replace(ITypedElement child, ITypedElement other) { if (fParent == null) { // TODO: I don't believe this code does anything useful but just in case // I'm leaving it in but disabling it for the shared document case // since all the subclasses that have been converted overrode the method anyway DocumentRangeNode src= null; String srcContents= ""; //$NON-NLS-1$ if (other != null) { src= (DocumentRangeNode) child; if (other instanceof IStreamContentAccessor) { try { srcContents= Utilities.readString((IStreamContentAccessor)other); } catch(CoreException ex) { // NeedWork CompareUIPlugin.log(ex); } } } if (child == null) // no destination: we have to add the contents into the parent add(srcContents, null, src); } nodeChanged(this); return child; } /** * Default implementation that calls {@link #internalSetContents(byte[])} * and then {@link #nodeChanged(DocumentRangeNode)}. Subclasses * may override but should then call {@link #nodeChanged(DocumentRangeNode)} * after the contents have been set. * @see org.eclipse.compare.IEditableContent#setContent(byte[]) */ @Override public void setContent(byte[] content) { internalSetContents(content); nodeChanged(this); } /** * Method that is invoked from {@link #setContent(byte[])}. By default, * this method does nothing. Subclasses may override. * @param content the new content * @since 3.3 */ protected void internalSetContents(byte[] content) { // By default, do nothing } @Override public String getCharset() { return UTF_16; } /** * Method that should be invoked whenever the contents of this node are * changed. the change is propagated to the parent if there is one. * @param node the node that has changed. * @since 3.3 */ protected void nodeChanged(DocumentRangeNode node) { if (fParent != null) fParent.nodeChanged(node); } /** * Implement {@link IAdaptable#getAdapter(Class)} in order to provide * an {@link ISharedDocumentAdapter} that provides the proper look up key based * on the input from which this structure node was created. The proper * shared document adapter is obtained by calling {@link #getAdapter(Class)} * on this node's parent if there is one. * @param adapter the adapter class to look up * @return the object adapted to the given class or null * @see IAdaptable#getAdapter(Class) * @since 3.3 */ @Override public T getAdapter(Class adapter) { if (adapter == ISharedDocumentAdapter.class && fParent != null) return fParent.getAdapter(adapter); return Platform.getAdapterManager().getAdapter(this, adapter); } @Override public boolean isReadOnly() { if (fParent != null) return fParent.isReadOnly(); return false; } @Override public IStatus validateEdit(Shell shell) { if (fParent != null) return fParent.validateEdit(shell); return Status.OK_STATUS; } /** * Return the parent of this node or null * if the node doesn't have a parent or the parent is not known. * @return the parent of this node or null */ public Object getParentNode() { return fParent; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy