// Copyright 2004-2014 Jim Voris
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package com.qumasoft.guitools.compare;
import com.qumasoft.guitools.merge.ColorManager;
import com.qumasoft.qvcslib.QumaAssert;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.font.TextAttribute;
import java.text.AttributedString;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JLabel;
import org.apache.commons.jrcs.diff.AddDelta;
import org.apache.commons.jrcs.diff.ChangeDelta;
import org.apache.commons.jrcs.diff.DeleteDelta;
import org.apache.commons.jrcs.diff.Delta;
import org.apache.commons.jrcs.diff.Diff;
import org.apache.commons.jrcs.diff.DifferentiationFailedException;
import org.apache.commons.jrcs.diff.Revision;
class ContentRow extends JLabel {
private static final long serialVersionUID = -4490555483346212013L;
// Create our logger object
private static final Logger LOGGER = Logger.getLogger("com.qumasoft.guitools.compare");
public static final byte ROWTYPE_NORMAL = 10;
public static final byte ROWTYPE_INSERT = 11;
public static final byte ROWTYPE_DELETE = 12;
public static final byte ROWTYPE_REPLACE = 13;
public static final byte ROWTYPE_BLANK = 14;
private byte rowType;
private int blankRowsAfter = 0;
private int blankRowsBefore = 0;
private int lineIndex;
private String actualText;
private Delta delta = null;
private byte[] fileACharacterTypeArray;
private boolean rowHadAnnotations = false;
ContentRow(Delta d) {
rowType = ROWTYPE_BLANK;
this.delta = d;
QumaAssert.isTrue(this.delta != null);
}
ContentRow(String formattedText, String actText, CompareFilesForGUI compareResult, int lineIdx, boolean isFirstFile) {
super(formattedText);
this.actualText = actText;
setRowType(determineRowType(compareResult, lineIdx, isFirstFile));
delta = compareResult.getDelta(lineIdx, isFirstFile);
int deletedLineCount;
int insertedLineCount;
this.lineIndex = lineIdx;
if (delta != null) {
deletedLineCount = delta.getOriginal().size();
insertedLineCount = delta.getRevised().size();
switch (rowType) {
case ROWTYPE_INSERT:
if (isFirstFile) {
blankRowsAfter = insertedLineCount;
}
break;
case ROWTYPE_DELETE:
if (!isFirstFile) {
blankRowsAfter = deletedLineCount;
}
break;
case ROWTYPE_REPLACE:
if (insertedLineCount > deletedLineCount) {
if (isFirstFile) {
int lastLineOfReplace = delta.getOriginal().last();
if (lineIdx == lastLineOfReplace) {
blankRowsAfter = insertedLineCount - deletedLineCount;
}
}
} else if (deletedLineCount > insertedLineCount) {
if (!isFirstFile) {
int lastLineOfReplace = delta.getRevised().last();
if (lineIdx == lastLineOfReplace) {
blankRowsAfter = deletedLineCount - insertedLineCount;
}
}
}
break;
case ROWTYPE_NORMAL:
if (isFirstFile) {
if (lineIdx == 0) {
blankRowsBefore = insertedLineCount;
} else if ((lineIdx == delta.getOriginal().first()) && (deletedLineCount == 0)) {
blankRowsBefore = insertedLineCount;
} else if (delta instanceof AddDelta) {
blankRowsAfter = insertedLineCount;
}
} else {
if (lineIdx == 0) {
blankRowsBefore = deletedLineCount;
}
if ((lineIdx == delta.getRevised().first()) && (insertedLineCount == 0)) {
blankRowsBefore = deletedLineCount;
} else if (delta instanceof DeleteDelta) {
blankRowsAfter = deletedLineCount;
}
}
break;
default:
LOGGER.log(Level.WARNING, "Unknown content row type for line index: [" + lineIdx + "]");
break;
}
}
}
Delta getDelta() {
return delta;
}
String getActualText() {
return actualText;
}
final void setRowType(byte rType) {
QumaAssert.isTrue((rType >= ROWTYPE_NORMAL) && (rType <= ROWTYPE_REPLACE));
this.rowType = rType;
}
byte getRowType() {
return rowType;
}
int getBlankRowsAfter() {
return blankRowsAfter;
}
int getBlankRowsBefore() {
return blankRowsBefore;
}
int getLineNumberIndex() {
return lineIndex;
}
final byte determineRowType(CompareFilesForGUI compareResult, int lineIdx, boolean isFirstFile) {
byte rType = compareResult.getRowType(lineIdx, isFirstFile);
return rType;
}
String getLineNumber() {
return Integer.toString(lineIndex + 1) + " ";
}
/**
* Decorate the differences between this row and its peer. This is used for replacement rows only.
* Use the apache algorithm.
*
* @param peerRow the peer row from the other file.
*/
void decorateDifferences(ContentRow peerRow) {
String s = getText();
String peer = peerRow.getText();
if (s.length() > 0 && peer.length() > 0) {
Byte[] sBytes = new Byte[s.getBytes().length];
Byte[] peerBytes = new Byte[peer.getBytes().length];
int sIndex = 0;
for (Byte sourceByte : s.getBytes()) {
sBytes[sIndex++] = sourceByte;
}
int peerIndex = 0;
for (Byte peerByte : peer.getBytes()) {
peerBytes[peerIndex++] = peerByte;
}
try {
Revision differences = Diff.diff(sBytes, peerBytes);
deduceCharacterAnnotations(differences, sBytes);
} catch (DifferentiationFailedException e) {
LOGGER.log(Level.WARNING, "Decoration failed: " + e.getLocalizedMessage());
}
}
}
@Override
public void paint(Graphics g) {
if ((getRowType() == ROWTYPE_REPLACE) && rowHadAnnotations) {
Graphics2D g2 = (Graphics2D) g;
String s = getText();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if (s.length() > 0) {
int index = 0;
AttributedString attributedString = new AttributedString(s, getFont().getAttributes());
try {
for (byte rType : fileACharacterTypeArray) {
switch (rType) {
case ContentRow.ROWTYPE_DELETE:
attributedString.addAttribute(TextAttribute.STRIKETHROUGH, null, index, index + 1);
break;
case ContentRow.ROWTYPE_REPLACE:
attributedString.addAttribute(TextAttribute.BACKGROUND, ColorManager.getReplaceCompareHiliteBackgroundColor(), index, index + 1);
break;
default:
break;
}
index++;
}
g2.drawString(attributedString.getIterator(), 0, getFont().getSize());
} catch (java.lang.IllegalArgumentException e) {
LOGGER.log(Level.WARNING, "bad replace indexes. begin index: ["
+ index + "] end index: ["
+ index + "]. String length: [" + s.length() + "]");
}
} else {
super.paint(g);
}
} else {
super.paint(g);
}
}
private void deduceCharacterAnnotations(Revision differences, Byte[] sBytes) {
// First get all the deltas...
Delta[] deltas = new Delta[differences.size()];
for (int i = 0; i < differences.size(); i++) {
deltas[i] = differences.getDelta(i);
}
fileACharacterTypeArray = new byte[sBytes.length];
// Set all the rows to default to NORMAL.
for (int i = 0; i < sBytes.length; i++) {
fileACharacterTypeArray[i] = ContentRow.ROWTYPE_NORMAL;
}
for (Delta characterDelta : deltas) {
byte rType;
if (characterDelta instanceof AddDelta) {
rType = ContentRow.ROWTYPE_INSERT;
rowHadAnnotations = true;
} else if (characterDelta instanceof ChangeDelta) {
rType = ContentRow.ROWTYPE_REPLACE;
rowHadAnnotations = true;
} else if (characterDelta instanceof DeleteDelta) {
rType = ContentRow.ROWTYPE_DELETE;
rowHadAnnotations = true;
} else {
continue; // this is goofy... and should never happen. We'll ignore the problem.
}
@SuppressWarnings("unchecked")
int firstA = characterDelta.getOriginal().first();
int lastA = characterDelta.getOriginal().last();
for (int j = firstA; j <= lastA; j++) {
fileACharacterTypeArray[j] = rType;
}
}
}
}