blob: 57073008bb6a17e59728123ab3575488927e544f [file] [log] [blame]
/*
* (c) Copyright IBM Corp. 2000, 2001.
* All Rights Reserved.
*/
package org.eclipse.compare.structuremergeviewer;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import org.eclipse.jface.text.*;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.compare.*;
import org.eclipse.compare.internal.Utilities;
import org.eclipse.compare.contentmergeviewer.*;
import org.eclipse.compare.contentmergeviewer.IDocumentRange;
import org.eclipse.compare.contentmergeviewer.TextMergeViewer;
/**
* A document range node represents a structural element
* when performing a structure compare of documents.
* <code>DocumentRangeNodes</code> 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 <code>DocumentRangeNode</code> maps to a range
* of characters in the document.
* <p>
* Since a <code>DocumentRangeNode</code> implements the <code>IStructureComparator</code>
* and <code>IStreamContentAccessor</code> 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.
* <p>
* Subclasses may add additional state collected while parsing the document.
* </p>
* @see Differencer
*/
public class DocumentRangeNode
implements IDocumentRange, IStructureComparator, IEditableContent, IStreamContentAccessor {
private static final boolean POS_UPDATE= true;
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;
/**
* Creates a new <code>DocumentRangeNode</code> for the given range within the specified
* document. The <code>typeCode</code> 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) {
fTypeCode= typeCode;
fID= id;
fBaseDocument= document;
fBaseDocument.addPositionCategory(RANGE_CATEGORY);
fRange= new Position(start, length);
if (POS_UPDATE) {
try {
document.addPosition(RANGE_CATEGORY, fRange);
} catch (BadPositionCategoryException ex) {
} catch (BadLocationException ex) {
}
}
}
/* (non Javadoc)
* see IDocumentRange.getDocument
*/
public IDocument getDocument() {
return fBaseDocument;
}
/* (non Javadoc)
* see IDocumentRange.getRange
*/
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 <code>equals</code> and <code>hashcode</code>.
*
* @return the node's id
*/
public String getId() {
return fID;
}
/**
* Sets this node's id.
* It is used in <code>equals</code> and <code>hashcode</code>.
*
* @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);
}
/* (non Javadoc)
* see IStructureComparator.getChildren
*/
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.
* <p>
* E.g. when parsing a Java document the "append position" of a <code>DocumentRangeNode</code>
* representating 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 (POS_UPDATE) {
fBaseDocument.removePosition(fAppendPosition);
try {
Position p= new Position(pos);
fBaseDocument.addPosition(RANGE_CATEGORY, p);
fAppendPosition= p;
} catch (BadPositionCategoryException ex) {
} catch (BadLocationException ex) {
// ignore
}
} else {
fAppendPosition= new Position(pos);
}
}
/**
* Returns the position that has been set with <code>setAppendPosition</code>.
* If <code>setAppendPosition</code> hasn't been called, the position after the last character
* of this range is returned.
*
* @return a position where text can be legally inserted
*/
public Position getAppendPosition() {
if (fAppendPosition == null) {
if (POS_UPDATE) {
try {
Position p= new Position(fBaseDocument.getLength());
fBaseDocument.addPosition(RANGE_CATEGORY, p);
fAppendPosition= p;
} catch (BadPositionCategoryException ex) {
} catch (BadLocationException ex) {
// ignore
}
} else {
fAppendPosition= new Position(fBaseDocument.getLength());
}
}
return fAppendPosition;
}
/**
* Implementation based on <code>getID</code>.
*/
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 <code>getID</code>.
*/
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= (DocumentRangeNode) otherParent.fChildren.get(i);
int i2= fChildren.indexOf(c1);
if (i2 >= 0) {
DocumentRangeNode c= (DocumentRangeNode) 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= (DocumentRangeNode) otherParent.fChildren.get(i);
int i2= fChildren.indexOf(c1);
if (i2 >= 0) {
DocumentRangeNode c= (DocumentRangeNode) 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) {
// ignore
}
}
}
/* (non Javadoc)
* see IStreamContentAccessor.getContents
*/
public InputStream getContents() {
String s;
try {
s= fBaseDocument.get(fRange.getOffset(), fRange.getLength());
} catch (BadLocationException ex) {
s= ""; //$NON-NLS-1$
}
return new ByteArrayInputStream(s.getBytes());
}
/* (non Javadoc)
* see IEditableContent.isEditable
*/
public boolean isEditable() {
return true;
}
/* (non Javadoc)
* see IEditableContent.replace
*/
public ITypedElement replace(ITypedElement child, ITypedElement other) {
DocumentRangeNode src= null;
String srcContents= ""; //$NON-NLS-1$
if (other != null) {
src= (DocumentRangeNode) child;
if (other instanceof IStreamContentAccessor) {
try {
InputStream is= ((IStreamContentAccessor)other).getContents();
byte[] bytes= Utilities.readBytes(is);
srcContents= new String(bytes);
} catch(CoreException ex) {
}
}
}
if (child != null) { // there is a destination
// if (child instanceof DocumentRangeNode) {
// DocumentRangeNode drn= (DocumentRangeNode) child;
//
// IDocument doc= drn.getDocument();
// Position range= drn.getRange();
// try {
// doc.replace(range.getOffset(), range.getLength(), srcContents);
// } catch (BadLocationException ex) {
// }
// }
} else {
// no destination: we have to add the contents into the parent
add(srcContents, null /*srcParentNode*/, src);
}
return child;
}
/* (non Javadoc)
* see IEditableContent.setContent
*/
public void setContent(byte[] content) {
// fBaseDocument.set(new String(content));
}
}