blob: 0aa2205a11b6449a80812d26fb169bf125528219 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 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.internal.core;
import java.util.ArrayList;
import java.util.List;
public class TextLineLCS extends LCS {
private final TextLine[] lines1;
private final TextLine[] lines2;
private TextLine[][] lcs;
public TextLineLCS(TextLine[] lines1, TextLine[] lines2) {
this.lines1 = lines1;
this.lines2 = lines2;
}
public TextLine[][] getResult() {
int length = getLength();
if (length == 0)
return new TextLine[2][0];
TextLine[][] result = new TextLine[2][];
// compact and shift the result
result[0] = compactAndShiftLCS(this.lcs[0], length, this.lines1);
result[1] = compactAndShiftLCS(this.lcs[1], length, this.lines2);
return result;
}
@Override
protected int getLength2() {
return this.lines2.length;
}
@Override
protected int getLength1() {
return this.lines1.length;
}
@Override
protected boolean isRangeEqual(int i1, int i2) {
return this.lines1[i1].sameText(this.lines2[i2]);
}
@Override
protected void setLcs(int sl1, int sl2) {
this.lcs[0][sl1] = this.lines1[sl1];
this.lcs[1][sl1] = this.lines2[sl2];
}
@Override
protected void initializeLcs(int length) {
this.lcs = new TextLine[2][length];
}
/**
* This method takes an lcs result interspersed with nulls, compacts it and
* shifts the LCS chunks as far towards the front as possible. This tends to
* produce good results most of the time.
*
* TODO: investigate what to do about comments. shifting either up or down
* hurts them
*
* @param lcsSide A subsequence of original, presumably it is the LCS of it and
* some other collection of lines
* @param len The number of non-null entries in lcs
* @param original The original sequence of lines of which lcs is a
* subsequence
*
* @return The subsequence lcs compacted and chunks shifted towards the
* front
*/
private TextLine[] compactAndShiftLCS(TextLine[] lcsSide, int len,
TextLine[] original) {
TextLine[] result = new TextLine[len];
if (len == 0) {
return result;
}
int j = 0;
while (lcsSide[j] == null) {
j++;
}
result[0] = lcsSide[j];
j++;
for (int i = 1; i < len; i++) {
while (lcsSide[j] == null) {
j++;
}
if (original[result[i - 1].lineNumber() + 1].sameText(lcsSide[j])) {
result[i] = original[result[i - 1].lineNumber() + 1];
} else {
result[i] = lcsSide[j];
}
j++;
}
return result;
}
/**
* Breaks the given text up into lines and returns an array of TextLine
* objects each corresponding to a single line, ordered according to the
* line number. That is result[i].lineNumber() == i and is the i'th line in
* text (starting from 0) Note: there are 1 more lines than there are
* newline characters in text. Corollary 1: if the last character is
* newline, the last line is empty Corollary 2: the empty string is 1 line
*
* @param text The text to extract lines from
* @return the array of TextLine object each corresponding to a line of text
*/
public static TextLine[] getTextLines(String text) {
List lines = new ArrayList();
int begin = 0;
int end = getEOL(text, 0);
int lineNum = 0;
while (end != -1) {
lines.add(new TextLine(lineNum++, text.substring(begin, end)));
begin = end + 1;
end = getEOL(text, begin);
if (end == begin && text.charAt(begin - 1) == '\r'
&& text.charAt(begin) == '\n') {
// we have '\r' followed by '\n', skip it
begin = end + 1;
end = getEOL(text, begin);
}
}
/*
* this is the last line, no more newline characters, so take the rest
* of the string
*/
lines.add(new TextLine(lineNum, text.substring(begin)));
TextLine[] aLines = new TextLine[lines.size()];
lines.toArray(aLines);
return aLines;
}
/**
* Returns the index of the next end of line marker ('\n' or '\r') after
* start
*
* @param text The string to examine
* @param start The location in the string to start looking
* @return the index such that text.charAt(index) == '\n' or '\r', -1 if not
* found
*/
private static int getEOL(String text, int start) {
int max = text.length();
for (int i = start; i < max; i++) {
char c = text.charAt(i);
if (c == '\n' || c == '\r') {
return i;
}
}
return -1;
}
/* used to store information about a single line of text */
public static class TextLine {
private int number; // the line number
private String text; // the actual text
public TextLine(int number, String text) {
this.number = number;
this.text = text;
}
/**
* Compares this TextLine to l and returns true if they have the same
* text
*
* @param l the TextLine to compare to
* @return true if this and l have the same text
*/
public boolean sameText(TextLine l) {
// compare the hashCode() first since that is much faster and most
// of the time the text lines won't match
return this.text.hashCode() == l.text.hashCode() && l.text.equals(this.text);
}
/**
* Returns the line number of this line
*
* @return the line number
*/
public int lineNumber() {
return this.number;
}
@Override
public String toString() {
return "" + this.number + " " + this.text + "\n"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
}