blob: 7eaeaa8b07f98621b73e53b0fa5bce1d3f641f34 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2019 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.compare.internal;
import org.eclipse.compare.ICompareFilter;
import org.eclipse.compare.contentmergeviewer.ITokenComparator;
import org.eclipse.compare.rangedifferencer.IRangeComparator;
import org.eclipse.core.internal.expressions.util.LRUCache;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
/**
* Implements the <code>IRangeComparator</code> interface for lines in a document.
* A <code>DocLineComparator</code> is used as the input for the <code>RangeDifferencer</code>
* engine to perform a line oriented compare on documents.
* <p>
* A <code>DocLineComparator</code> doesn't know anything about line separators because
* its notion of lines is solely defined in the underlying <code>IDocument</code>.
*/
public class DocLineComparator implements ITokenComparator {
private IDocument fDocument;
private int fLineOffset;
private int fLineCount;
private int fLength;
private boolean fIgnoreWhiteSpace;
private ICompareFilter[] fCompareFilters;
private char fContributor;
private LRUCache fCompareFilterCache;
/**
* Creates a <code>DocLineComparator</code> for the given document range.
* ignoreWhiteSpace controls whether comparing lines (in method
* <code>rangesEqual<code>) should ignore whitespace.
*
* @param document the document from which the lines are taken
* @param region if non-<code>null</code> only lines within this range are taken
* @param ignoreWhiteSpace if <code>true</code> white space is ignored when comparing lines
*/
public DocLineComparator(IDocument document, IRegion region,
boolean ignoreWhiteSpace) {
this(document, region, ignoreWhiteSpace, null, '?');
}
/**
* Creates a <code>DocLineComparator</code> for the given document range.
* ignoreWhiteSpace controls whether comparing lines (in method
* <code>rangesEqual<code>) should ignore whitespace. Compare filters may be used
* to affect the detection of line differences.
*
* @param document
* the document from which the lines are taken
* @param region
* if non-<code>null</code> only lines within this range are
* taken
* @param ignoreWhiteSpace
* if <code>true</code> white space is ignored when comparing
* lines
* @param compareFilters
* the active compare filters for the compare
* @param contributor
* contributor of document
*/
public DocLineComparator(IDocument document, IRegion region,
boolean ignoreWhiteSpace, ICompareFilter[] compareFilters,
char contributor) {
fDocument = document;
fIgnoreWhiteSpace = ignoreWhiteSpace;
fCompareFilters = compareFilters;
fContributor = contributor;
boolean cacheFilteredLines = false;
if (compareFilters != null && compareFilters.length > 0) {
cacheFilteredLines = true;
for (ICompareFilter compareFilter : compareFilters) {
if (!compareFilter.canCacheFilteredRegions()) {
cacheFilteredLines = false;
break;
}
}
}
fCompareFilterCache = (cacheFilteredLines) ? new LRUCache(1024) : null;
fLineOffset = 0;
if (region != null) {
fLength = region.getLength();
int start = region.getOffset();
try {
fLineOffset = fDocument.getLineOfOffset(start);
} catch (BadLocationException ex) {
// silently ignored
}
if (fLength == 0) {
// optimization, empty documents have one line
fLineCount = 1;
} else {
int endLine = fDocument.getNumberOfLines();
try {
endLine = fDocument.getLineOfOffset(start + fLength);
} catch (BadLocationException ex) {
// silently ignored
}
fLineCount = endLine - fLineOffset + 1;
}
} else {
fLength = document.getLength();
fLineCount = fDocument.getNumberOfLines();
}
}
/**
* Returns the number of lines in the document.
*
* @return number of lines
*/
@Override
public int getRangeCount() {
return fLineCount;
}
/* (non Javadoc)
* see ITokenComparator.getTokenStart
*/
@Override
public int getTokenStart(int line) {
try {
IRegion r= fDocument.getLineInformation(fLineOffset + line);
return r.getOffset();
} catch (BadLocationException ex) {
return fDocument.getLength();
}
}
/* (non Javadoc)
* Returns the length of the given line.
* see ITokenComparator.getTokenLength
*/
@Override
public int getTokenLength(int line) {
return getTokenStart(line+1) - getTokenStart(line);
}
/**
* Returns <code>true</code> if a line given by the first index
* matches a line specified by the other <code>IRangeComparator</code> and index.
*
* @param thisIndex the number of the line within this range comparator
* @param otherComparator the range comparator to compare this with
* @param otherIndex the number of the line within the other comparator
* @return <code>true</code> if the lines are equal
*/
@Override
public boolean rangesEqual(int thisIndex, IRangeComparator otherComparator, int otherIndex) {
if (otherComparator != null && otherComparator.getClass() == getClass()) {
DocLineComparator other= (DocLineComparator) otherComparator;
if (fIgnoreWhiteSpace) {
String[] linesToCompare = extract(thisIndex, otherIndex, other, false);
return compare(linesToCompare[0], linesToCompare[1]);
}
int tlen= getTokenLength(thisIndex);
int olen= other.getTokenLength(otherIndex);
if (tlen == olen) {
String[] linesToCompare = extract(thisIndex, otherIndex, other, false);
return linesToCompare[0].equals(linesToCompare[1]);
} else if (fCompareFilters != null && fCompareFilters.length > 0) {
String[] linesToCompare = extract(thisIndex, otherIndex, other, true);
return linesToCompare[0].equals(linesToCompare[1]);
}
}
return false;
}
/**
* Aborts the comparison if the number of tokens is too large.
*
* @param length a number on which to base the decision whether to return
* <code>true</code> or <code>false</code>
* @param maxLength another number on which to base the decision whether to return
* <code>true</code> or <code>false</code>
* @param other the other <code>IRangeComparator</code> to compare with
* @return <code>true</code> to avoid a too lengthy range comparison
*/
@Override
public boolean skipRangeComparison(int length, int maxLength, IRangeComparator other) {
return false;
}
//---- private methods
private String[] extract(int thisIndex, int otherIndex,
DocLineComparator other, boolean includeSeparator) {
String[] extracts = new String[2];
if (fCompareFilters != null && fCompareFilters.length > 0) {
if (fCompareFilterCache != null
&& other.fCompareFilterCache != null) {
extracts[0] = (String) fCompareFilterCache.get(Integer.valueOf(
thisIndex));
if (extracts[0] == null) {
extracts[0] = Utilities.applyCompareFilters(
extract(thisIndex, includeSeparator), fContributor,
other.extract(otherIndex, includeSeparator), other.fContributor,
fCompareFilters);
fCompareFilterCache
.put(Integer.valueOf(thisIndex), extracts[0]);
}
extracts[1] = (String) other.fCompareFilterCache
.get(Integer.valueOf(otherIndex));
if (extracts[1] == null) {
extracts[1] = Utilities.applyCompareFilters(
other.extract(otherIndex, includeSeparator), other.fContributor,
extract(thisIndex, includeSeparator), fContributor, fCompareFilters);
other.fCompareFilterCache.put(Integer.valueOf(otherIndex),
extracts[1]);
}
} else {
String thisLine = extract(thisIndex, includeSeparator);
String otherLine = other.extract(otherIndex, includeSeparator);
extracts = new String[] {
Utilities.applyCompareFilters(thisLine, fContributor,
otherLine, other.fContributor, fCompareFilters),
Utilities.applyCompareFilters(otherLine,
other.fContributor, thisLine, fContributor,
fCompareFilters) };
}
} else {
extracts = new String[] { extract(thisIndex, includeSeparator),
other.extract(otherIndex, includeSeparator) };
}
return extracts;
}
/**
* Extract a single line from the underlying document.
*
* @param line the number of the line to extract
* @param whether to include the line separator
* @return the contents of the line as a String
*/
private String extract(int line, boolean includeSeparator) {
if (line < fLineCount) {
try {
if (includeSeparator)
return fDocument.get(fDocument.getLineOffset(line),
fDocument.getLineLength(line));
IRegion r = fDocument.getLineInformation(fLineOffset + line);
return fDocument.get(r.getOffset(), r.getLength());
} catch (BadLocationException e) {
// silently ignored
}
}
return ""; //$NON-NLS-1$
}
private boolean compare(String s1, String s2) {
int l1= s1.length();
int l2= s2.length();
int c1= 0, c2= 0;
int i1= 0, i2= 0;
while (c1 != -1) {
c1= -1;
while (i1 < l1) {
char c= s1.charAt(i1++);
if (! Character.isWhitespace(c)) {
c1= c;
break;
}
}
c2= -1;
while (i2 < l2) {
char c= s2.charAt(i2++);
if (! Character.isWhitespace(c)) {
c2= c;
break;
}
}
if (c1 != c2)
return false;
}
return true;
}
}