package com.delcyon.capo.webapp.widgets;
import java.io.ByteArrayInputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.delcyon.capo.parsers.Tokenizer;
import com.delcyon.capo.parsers.Tokenizer.CharacterType;
import eu.webtoolkit.jwt.Utils;
import eu.webtoolkit.jwt.WCompositeWidget;
import eu.webtoolkit.jwt.WContainerWidget;
import eu.webtoolkit.jwt.WTable;
import eu.webtoolkit.jwt.WTableCell;
import eu.webtoolkit.jwt.WText;
/**
* Will take a formatted diff string and produce a side by side highlighted view of that diff.
* @author jeremiah
*
*/
public class WDiffWidget extends WCompositeWidget
{
private WTable table = new WTable();
private String baseHeader = "";
private String modHeader = "";
/**
* Supported diff formats
* @author jeremiah
*
*/
public enum DiffFormat
{
CAPO
}
public WDiffWidget()
{
WContainerWidget containerWidget = new WContainerWidget();
containerWidget.addWidget(table);
setImplementation(containerWidget);
setStyleClass("diff");
table.setHeaderCount(1);
}
public void setDiff(String differences, DiffFormat format) throws Exception
{
table.clear();
int headerOffset = insertHeaders();
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(differences.getBytes());
Tokenizer tokenizer = new Tokenizer(byteArrayInputStream);
tokenizer.resetSyntax();
tokenizer.setCharRangeType(0, 255, CharacterType.ALPHA);
tokenizer.setCharType('\n', CharacterType.EOL);
tokenizer.setCharType('\r', CharacterType.EOL);
int baseRow = headerOffset;
int modRow = headerOffset;
String lastType = "=";
while(tokenizer.hasMore())
{
tokenizer.nextToken();
String line = tokenizer.getValue();
if(line == null)
{
line ="";
}
Matcher matcher = Pattern.compile("([-+=])\\((\\d+),(\\d+)\\)\\[\\d+\\](.*)").matcher(line);
if(matcher.matches() || tokenizer.hasMore() == false)
{
//System.out.println("data = "+matcher.group(1) +"==>"+line);
String type = null;
String text = null;
if(tokenizer.hasMore() == false)
{
type = "=";
text = "";
}
else
{
type = matcher.group(1);
text = Utils.htmlEncode(matcher.group(4));
}
//check to see if were switching types, and not from an equals, as the system should be in balance at that point
if(type.equals(lastType) == false && lastType.equals("=") == false)
{
if(type.equals("=")) //check to see if we're moving out of a mod
{
while(baseRow < modRow) //we just finished an addition, so add all of the blank lines in the base side
{
styledCell(baseRow, 1,"diff_base_addition").addWidget(new WText(""));
styledCell(baseRow, 0,"diff_base_addition_linenumber").addWidget(new WText(""));
baseRow++;
}
while(baseRow > modRow)
{
styledCell(modRow, 3,"diff_mod_deletion").addWidget(new WText(""));
styledCell(modRow, 2,"diff_mod_deletion_linenumber").addWidget(new WText(""));
modRow++;
}
}
}
if(tokenizer.hasMore() == false)
{
break; //we we're just here to finish flushing out the empty rows
}
lastType = type;
switch (type)
{
case "+":
//This all takes care of hightlighing on an indivdual line if we find ourselves across from an already existing base or mod that we're going to fill out here
if(modRow < baseRow) //check too see if we're filling in a mod
{
WText wtext = (WText) table.getElementAt(modRow, 1).getWidget(0);
int[] spanPos = getLineDifferences(wtext.getText().getValue(),text);
if(spanPos[0] != -1 && spanPos[1] != -1 && spanPos[2] != -1) //skip if we're missing something
{
text = text.substring(0,spanPos[0])+"<span class='diff_mod_text'>"+text.substring(spanPos[0],spanPos[2])+"</span>"+text.substring(spanPos[2]);
String otherText = wtext.getText().getValue();
otherText = otherText.substring(0,spanPos[0])+"<span class='diff_base_text'>"+otherText.substring(spanPos[0],spanPos[1])+"</span>"+otherText.substring(spanPos[1]);
wtext.setText(otherText);
}
}
styledCell(modRow, 3,"diff_mod_addtition").addWidget(new WText(text));
styledCell(modRow, 2,"diff_mod_addition_linenumber").addWidget(new WText(matcher.group(3)));
modRow++;
break;
case "-":
//This all takes care of hightlighing on an indivdual line if we find ourselves across from an already existing base or mod that we're going to fill out here
if(modRow > baseRow) //check too see if we're filling in a base
{
WText wtext = (WText) table.getElementAt(baseRow, 3).getWidget(0);
int[] spanPos = getLineDifferences(wtext.getText().getValue(),text);
if(spanPos[0] != -1 && spanPos[1] != -1 && spanPos[2] != -1) //skip if we're missing something
{
text = text.substring(0,spanPos[0])+"<span class='diff_base_text'>"+text.substring(spanPos[0],spanPos[2])+"</span>"+text.substring(spanPos[2]);
String otherText = wtext.getText().getValue();
otherText = otherText.substring(0,spanPos[0])+"<span class='diff_mod_text'>"+otherText.substring(spanPos[0],spanPos[1])+"</span>"+otherText.substring(spanPos[1]);
wtext.setText(otherText);
}
}
styledCell(baseRow, 1,"diff_base_deletion").addWidget(new WText(text));
styledCell(baseRow, 0,"diff_base_deletion_linenumber").addWidget(new WText(matcher.group(2)));
baseRow++;
break;
default: //dealing with an '=' so just do the same for both sides
styledCell(baseRow, 1,"diff_base_same").addWidget(new WText(text));
styledCell(modRow, 3,"diff_mod_same").addWidget(new WText(text));
styledCell(baseRow, 0,"diff_base_same_linenumber").addWidget(new WText(matcher.group(2)));
styledCell(modRow, 2,"diff_mod_same_linenumber").addWidget(new WText(matcher.group(3)));
baseRow++;
modRow++;
break;
}
}
}
}
/**
* takes two strings and computes the positions of the differences between them
* @param baseText
* @param modText
* @return int[3] where 0 = startPos, 1 = baseEndPos, 2 = modEndPos
*/
private int[] getLineDifferences(String baseText, String modText)
{
int[] lineDiffs = new int[]{-1,-1,-1};
//first find start position, by finding first place where chars don't match
for(int index = 0; index < baseText.length() && index < modText.length(); index++ )
{
if(lineDiffs[0] == -1 && baseText.charAt(index) != modText.charAt(index))
{
lineDiffs[0] = index;
}
int baseReversedIndex = baseText.length()-1-index;
int modReveresedIndex = modText.length()-1-index;
if(baseReversedIndex <= lineDiffs[0] && lineDiffs[1] == -1) //break if we've passsed each other
{
lineDiffs[1] = lineDiffs[0];
lineDiffs[2] = modReveresedIndex;
}
if(modReveresedIndex <= lineDiffs[0] && lineDiffs[2] == -1) //break if we've passsed each other
{
lineDiffs[2] = lineDiffs[0];
lineDiffs[1] = baseReversedIndex;
}
if(lineDiffs[1] == -1 && baseText.charAt(baseReversedIndex) != modText.charAt(modReveresedIndex))
{
lineDiffs[1] = baseReversedIndex+1;
lineDiffs[2] = modReveresedIndex+1;
}
if(lineDiffs[0] != -1 && lineDiffs[1] != -1 && lineDiffs[2] != -1) //break if we know everything
{
break;
}
}
//make sure that we don't set an end position before a start position. Can happen when we've added stuff to the middle of a string
if(lineDiffs[0] > lineDiffs[1])
{
lineDiffs[1] = lineDiffs[0];
}
if(lineDiffs[0] > lineDiffs[2])
{
lineDiffs[2] = lineDiffs[0];
}
return lineDiffs;
}
/**
* quick method to make our code a little shorter
* @param row
* @param column
* @param styleClass
* @return
*/
private WTableCell styledCell(int row, int column,String styleClass)
{
WTableCell cell = table.getElementAt(row, column);
cell.setStyleClass(styleClass);
return cell;
}
public void setHeaders(String baseHeader, String modHeader)
{
this.baseHeader = baseHeader+""; //insure not null
this.modHeader = modHeader+"";
insertHeaders();
}
public String getBaseHeader()
{
return baseHeader;
}
public String getModHeader()
{
return modHeader;
}
private int insertHeaders()
{
WTableCell cell = table.getElementAt(0, 0);
cell.setStyleClass("diff_base_header");
cell.setColumnSpan(2);
cell.addWidget(new WText(getBaseHeader()));
cell = table.getElementAt(0, 2);
cell.setStyleClass("diff_base_header");
cell.setColumnSpan(2);
cell.addWidget(new WText(getModHeader()));
return 1;
}
}