mirror of
https://github.com/java-diff-utils/java-diff-utils.git
synced 2026-03-13 10:11:17 +08:00
merge 1.2 branch to trunk
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Java Diff Utills Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
*/
|
||||
package difflib;
|
||||
|
||||
import java.util.List;
|
||||
@@ -26,59 +26,60 @@ import java.util.List;
|
||||
* @author <a href="dm.naumenko@gmail.com">Dmitry Naumenko</a>
|
||||
*/
|
||||
public class ChangeDelta extends Delta {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public ChangeDelta(Chunk original, Chunk revised) {
|
||||
super(original, revised);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @throws PatchFailedException
|
||||
*/
|
||||
@Override
|
||||
public void applyTo(List<Object> target) throws PatchFailedException {
|
||||
verify(target);
|
||||
int position = getOriginal().getPosition();
|
||||
int size = getOriginal().getSize();
|
||||
for (int i = 0; i < size; i++) {
|
||||
target.remove(position);
|
||||
}
|
||||
int i = 0;
|
||||
for (Object line: getRevised().getLines()) {
|
||||
target.add(position + i, line);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void restore(List<Object> target) {
|
||||
int position = getRevised().getPosition();
|
||||
int size = getRevised().getSize();
|
||||
for (int i = 0; i < size; i++) {
|
||||
target.remove(position);
|
||||
}
|
||||
int i = 0;
|
||||
for (Object line: getOriginal().getLines()) {
|
||||
target.add(position + i, line);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void verify(List<?> target) throws PatchFailedException {
|
||||
getOriginal().verify(target);
|
||||
if (getOriginal().getPosition() > target.size()) {
|
||||
throw new PatchFailedException("Incorrect patch for delta: " +
|
||||
"delta original position > target size");
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public ChangeDelta(Chunk original, Chunk revised) {
|
||||
super(original, revised);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @throws PatchFailedException
|
||||
*/
|
||||
@Override
|
||||
public void applyTo(List<Object> target) throws PatchFailedException {
|
||||
verify(target);
|
||||
int position = getOriginal().getPosition();
|
||||
int size = getOriginal().getSize();
|
||||
for (int i = 0; i < size; i++) {
|
||||
target.remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (Object line : getRevised().getLines()) {
|
||||
target.add(position + i, line);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void restore(List<Object> target) {
|
||||
int position = getRevised().getPosition();
|
||||
int size = getRevised().getSize();
|
||||
for (int i = 0; i < size; i++) {
|
||||
target.remove(position);
|
||||
}
|
||||
int i = 0;
|
||||
for (Object line : getOriginal().getLines()) {
|
||||
target.add(position + i, line);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void verify(List<?> target) throws PatchFailedException {
|
||||
getOriginal().verify(target);
|
||||
if (getOriginal().getPosition() > target.size()) {
|
||||
throw new PatchFailedException("Incorrect patch for delta: "
|
||||
+ "delta original position > target size");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Java Diff Utills Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
*/
|
||||
package difflib;
|
||||
|
||||
import java.util.*;
|
||||
@@ -23,149 +23,168 @@ import java.util.*;
|
||||
/**
|
||||
* Holds the information about the part of text involved in the diff process
|
||||
*
|
||||
* <p>Text is represented as <code>Object[]</code> because
|
||||
* the diff engine is capable of handling more than plain ascci. In fact,
|
||||
* arrays or lists of any type that implements
|
||||
* {@link java.lang.Object#hashCode hashCode()} and
|
||||
* {@link java.lang.Object#equals equals()}
|
||||
* correctly can be subject to differencing using this
|
||||
* library.</p>
|
||||
* <p>
|
||||
* Text is represented as <code>Object[]</code> because the diff engine is
|
||||
* capable of handling more than plain ascci. In fact, arrays or lists of any
|
||||
* type that implements {@link java.lang.Object#hashCode hashCode()} and
|
||||
* {@link java.lang.Object#equals equals()} correctly can be subject to
|
||||
* differencing using this library.
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="dm.naumenko@gmail.com>Dmitry Naumenko</a>
|
||||
*/
|
||||
public class Chunk {
|
||||
private int position;
|
||||
private int size;
|
||||
private List<?> lines;
|
||||
|
||||
/**
|
||||
* Creates a chunk and saves a copy of affected lines
|
||||
*
|
||||
* @param position the start position
|
||||
* @param size the size of a Chunk
|
||||
* @param lines the affected lines
|
||||
*/
|
||||
public Chunk(int position, int size, List<?> lines) {
|
||||
this.position = position;
|
||||
this.size = size;
|
||||
this.lines = lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a chunk and saves a copy of affected lines
|
||||
*
|
||||
* @param position the start position
|
||||
* @param size the size of a Chunk
|
||||
* @param lines the affected lines
|
||||
*/
|
||||
public Chunk(int position, int size, Object[] lines) {
|
||||
this.position = position;
|
||||
this.size = size;
|
||||
this.lines = Arrays.asList(lines);
|
||||
}
|
||||
|
||||
private int position;
|
||||
private int size;
|
||||
private List<?> lines;
|
||||
|
||||
/**
|
||||
* Verifies that this chunk's saved text matches the corresponding
|
||||
* text in the given sequence.
|
||||
* @param target the sequence to verify against.
|
||||
* Creates a chunk and saves a copy of affected lines
|
||||
*
|
||||
* @param position
|
||||
* the start position
|
||||
* @param size
|
||||
* the size of a Chunk
|
||||
* @param lines
|
||||
* the affected lines
|
||||
*/
|
||||
public void verify(List<?> target) throws PatchFailedException{
|
||||
public Chunk(int position, int size, List<?> lines) {
|
||||
this.position = position;
|
||||
this.size = size;
|
||||
this.lines = lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a chunk and saves a copy of affected lines
|
||||
*
|
||||
* @param position
|
||||
* the start position
|
||||
* @param size
|
||||
* the size of a Chunk
|
||||
* @param lines
|
||||
* the affected lines
|
||||
*/
|
||||
public Chunk(int position, int size, Object[] lines) {
|
||||
this.position = position;
|
||||
this.size = size;
|
||||
this.lines = Arrays.asList(lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that this chunk's saved text matches the corresponding text in
|
||||
* the given sequence.
|
||||
*
|
||||
* @param target
|
||||
* the sequence to verify against.
|
||||
*/
|
||||
public void verify(List<?> target) throws PatchFailedException {
|
||||
if (last() > target.size()) {
|
||||
throw new PatchFailedException("Incorrect Chunk: the position of chunk > target size");
|
||||
}
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (!target.get(position + i).equals(lines.get(i))) {
|
||||
throw new PatchFailedException("Incorrect Chunk: the chunk content doesn't match the target");
|
||||
throw new PatchFailedException(
|
||||
"Incorrect Chunk: the chunk content doesn't match the target");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the start position of chunk in the text
|
||||
*/
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
/**
|
||||
* @param position the start position to set
|
||||
*/
|
||||
public void setPosition(int position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the size of Chunk (size of affected lines)
|
||||
*/
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
/**
|
||||
* @param size the size of affected lines to set
|
||||
*/
|
||||
public void setSize(int size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the affected lines
|
||||
*/
|
||||
public List<?> getLines() {
|
||||
return lines;
|
||||
}
|
||||
/**
|
||||
* @param lines the affected lines to set
|
||||
*/
|
||||
public void setLines(List<?> lines) {
|
||||
this.lines = lines;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the start position of chunk in the text
|
||||
*/
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param position
|
||||
* the start position to set
|
||||
*/
|
||||
public void setPosition(int position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the size of Chunk (size of affected lines)
|
||||
*/
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param size
|
||||
* the size of affected lines to set
|
||||
*/
|
||||
public void setSize(int size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the affected lines
|
||||
*/
|
||||
public List<?> getLines() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param lines
|
||||
* the affected lines to set
|
||||
*/
|
||||
public void setLines(List<?> lines) {
|
||||
this.lines = lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the last line of the chunk.
|
||||
*/
|
||||
public int last(){
|
||||
public int last() {
|
||||
return getPosition() + getSize() - 1;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((lines == null) ? 0 : lines.hashCode());
|
||||
result = prime * result + position;
|
||||
result = prime * result + size;
|
||||
return result;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Chunk other = (Chunk) obj;
|
||||
if (lines == null) {
|
||||
if (other.lines != null)
|
||||
return false;
|
||||
} else if (!lines.equals(other.lines))
|
||||
return false;
|
||||
if (position != other.position)
|
||||
return false;
|
||||
if (size != other.size)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[position: " + position + ", size: " + size + ", lines: " + lines + "]";
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((lines == null) ? 0 : lines.hashCode());
|
||||
result = prime * result + position;
|
||||
result = prime * result + size;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Chunk other = (Chunk) obj;
|
||||
if (lines == null) {
|
||||
if (other.lines != null)
|
||||
return false;
|
||||
} else if (!lines.equals(other.lines))
|
||||
return false;
|
||||
if (position != other.position)
|
||||
return false;
|
||||
if (size != other.size)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[position: " + position + ", size: " + size + ", lines: " + lines + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Java Diff Utills Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
*/
|
||||
package difflib;
|
||||
|
||||
import java.util.List;
|
||||
@@ -26,48 +26,49 @@ import java.util.List;
|
||||
* @author <a href="dm.naumenko@gmail.com">Dmitry Naumenko</a>
|
||||
*/
|
||||
public class DeleteDelta extends Delta {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public DeleteDelta(Chunk original, Chunk revised) {
|
||||
super(original, revised);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @throws PatchFailedException
|
||||
*/
|
||||
@Override
|
||||
public void applyTo(List<Object> target) throws PatchFailedException {
|
||||
verify(target);
|
||||
int position = getOriginal().getPosition();
|
||||
int size = getOriginal().getSize();
|
||||
for (int i = 0; i < size; i++) {
|
||||
target.remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void restore(List<Object> target) {
|
||||
int position = this.getRevised().getPosition();
|
||||
List<?> lines = this.getOriginal().getLines();
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
target.add(position + i, lines.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(List<?> target) throws PatchFailedException {
|
||||
getOriginal().verify(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[DeleteDelta, position: " + getOriginal().getPosition() + ", lines: " +
|
||||
getOriginal().getLines() + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public DeleteDelta(Chunk original, Chunk revised) {
|
||||
super(original, revised);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @throws PatchFailedException
|
||||
*/
|
||||
@Override
|
||||
public void applyTo(List<Object> target) throws PatchFailedException {
|
||||
verify(target);
|
||||
int position = getOriginal().getPosition();
|
||||
int size = getOriginal().getSize();
|
||||
for (int i = 0; i < size; i++) {
|
||||
target.remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void restore(List<Object> target) {
|
||||
int position = this.getRevised().getPosition();
|
||||
List<?> lines = this.getOriginal().getLines();
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
target.add(position + i, lines.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(List<?> target) throws PatchFailedException {
|
||||
getOriginal().verify(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[DeleteDelta, position: " + getOriginal().getPosition() + ", lines: "
|
||||
+ getOriginal().getLines() + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Java Diff Utills Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
*/
|
||||
package difflib;
|
||||
|
||||
import java.util.*;
|
||||
@@ -26,105 +26,111 @@ import java.util.*;
|
||||
* @author <a href="dm.naumenko@gmail.com">Dmitry Naumenko</a>
|
||||
*/
|
||||
public abstract class Delta {
|
||||
private Chunk original;
|
||||
private Chunk revised;
|
||||
|
||||
/**
|
||||
* Construct the delta for original and revised chunks
|
||||
*
|
||||
* @param original chunk describes the original text
|
||||
* @param revised chunk describes the revised text
|
||||
*/
|
||||
public Delta(Chunk original, Chunk revised) {
|
||||
this.original = original;
|
||||
this.revised = revised;
|
||||
}
|
||||
|
||||
private Chunk original;
|
||||
private Chunk revised;
|
||||
|
||||
/**
|
||||
* Construct the delta for original and revised chunks
|
||||
*
|
||||
* @param original chunk describes the original text
|
||||
* @param revised chunk describes the revised text
|
||||
*/
|
||||
public Delta(Chunk original, Chunk revised) {
|
||||
this.original = original;
|
||||
this.revised = revised;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that this delta can be used to patch the given text.
|
||||
*
|
||||
* @param target the text to patch.
|
||||
* @throws PatchFailedException if the patch cannot be applied.
|
||||
*/
|
||||
public abstract void verify(List<?> target) throws PatchFailedException;
|
||||
|
||||
/**
|
||||
* Applies this delta as the patch for a given target
|
||||
*
|
||||
* @param target the given target
|
||||
* @throws PatchFailedException
|
||||
*/
|
||||
public abstract void applyTo(List<Object> target) throws PatchFailedException;
|
||||
|
||||
/**
|
||||
* Cancel this delta for a given revised text. The action is opposite to patch.
|
||||
*
|
||||
* @param target the given revised text
|
||||
*/
|
||||
public abstract void restore(List<Object> target);
|
||||
|
||||
/**
|
||||
* @return the Chunk describing the original text
|
||||
*/
|
||||
public Chunk getOriginal() {
|
||||
return original;
|
||||
}
|
||||
/**
|
||||
* @param original the Chunk describing the original text to set
|
||||
*/
|
||||
public void setOriginal(Chunk original) {
|
||||
this.original = original;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Chunk describing the revised text
|
||||
*/
|
||||
public Chunk getRevised() {
|
||||
return revised;
|
||||
}
|
||||
/**
|
||||
* @param revised the Chunk describing the revised text to set
|
||||
*/
|
||||
public void setRevised(Chunk revised) {
|
||||
this.revised = revised;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result
|
||||
+ ((original == null) ? 0 : original.hashCode());
|
||||
result = prime * result + ((revised == null) ? 0 : revised.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Delta other = (Delta) obj;
|
||||
if (original == null) {
|
||||
if (other.original != null)
|
||||
return false;
|
||||
} else if (!original.equals(other.original))
|
||||
return false;
|
||||
if (revised == null) {
|
||||
if (other.revised != null)
|
||||
return false;
|
||||
} else if (!revised.equals(other.revised))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Applies this delta as the patch for a given target
|
||||
*
|
||||
* @param target the given target
|
||||
* @throws PatchFailedException
|
||||
*/
|
||||
public abstract void applyTo(List<Object> target) throws PatchFailedException;
|
||||
|
||||
/**
|
||||
* Cancel this delta for a given revised text. The action is opposite to
|
||||
* patch.
|
||||
*
|
||||
* @param target the given revised text
|
||||
*/
|
||||
public abstract void restore(List<Object> target);
|
||||
|
||||
/**
|
||||
* @return the Chunk describing the original text
|
||||
*/
|
||||
public Chunk getOriginal() {
|
||||
return original;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param original the Chunk describing the original text to set
|
||||
*/
|
||||
public void setOriginal(Chunk original) {
|
||||
this.original = original;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Chunk describing the revised text
|
||||
*/
|
||||
public Chunk getRevised() {
|
||||
return revised;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param revised the Chunk describing the revised text to set
|
||||
*/
|
||||
public void setRevised(Chunk revised) {
|
||||
this.revised = revised;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((original == null) ? 0 : original.hashCode());
|
||||
result = prime * result + ((revised == null) ? 0 : revised.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Delta other = (Delta) obj;
|
||||
if (original == null) {
|
||||
if (other.original != null)
|
||||
return false;
|
||||
} else if (!original.equals(other.original))
|
||||
return false;
|
||||
if (revised == null) {
|
||||
if (other.revised != null)
|
||||
return false;
|
||||
} else if (!revised.equals(other.revised))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Java Diff Utills Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
*/
|
||||
package difflib;
|
||||
|
||||
import java.util.*;
|
||||
@@ -26,26 +26,24 @@ import java.util.*;
|
||||
* @author <a href="dm.naumenko@gmail.com">Dmitry Naumenko</a>
|
||||
*/
|
||||
public interface DiffAlgorithm {
|
||||
|
||||
|
||||
/**
|
||||
* Computes the difference between the original
|
||||
* sequence and the revised sequence and returns it
|
||||
* as a {@link difflib.Patch Patch} object.
|
||||
*
|
||||
* Computes the difference between the original sequence and the revised
|
||||
* sequence and returns it as a {@link difflib.Patch Patch} object.
|
||||
*
|
||||
* @param original the original text
|
||||
* @param revised the revised text
|
||||
* @return the patch
|
||||
*/
|
||||
public Patch diff(Object[] original, Object[] revised);
|
||||
|
||||
public Patch diff(Object[] original, Object[] revised);
|
||||
|
||||
/**
|
||||
* Computes the difference between the original
|
||||
* sequence and the revised sequence and returns it
|
||||
* as a {@link difflib.Patch Patch} object.
|
||||
*
|
||||
* Computes the difference between the original sequence and the revised
|
||||
* sequence and returns it as a {@link difflib.Patch Patch} object.
|
||||
*
|
||||
* @param original the original text
|
||||
* @param revised the revised text
|
||||
* @return the patch
|
||||
*/
|
||||
public Patch diff(List<?> original, List<?> revised);
|
||||
public Patch diff(List<?> original, List<?> revised);
|
||||
}
|
||||
|
||||
@@ -15,23 +15,22 @@
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Java Diff Utills Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
*/
|
||||
package difflib;
|
||||
|
||||
/**
|
||||
* Base class for all exceptions emanating from this package.
|
||||
*
|
||||
*
|
||||
* @author <a href="mailto:juanco@suigeneris.org">Juanco Anez</a>
|
||||
*/
|
||||
public class DiffException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public DiffException() {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public DiffException() {
|
||||
}
|
||||
|
||||
|
||||
public DiffException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,112 +15,120 @@
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Java Diff Utills Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
*/
|
||||
package difflib;
|
||||
|
||||
/**
|
||||
* Describes the diff row in form [tag, oldLine, newLine) for showing the difference between two
|
||||
* texts
|
||||
* Describes the diff row in form [tag, oldLine, newLine) for showing the
|
||||
* difference between two texts
|
||||
*
|
||||
* @author <a href="dm.naumenko@gmail.com">Dmitry Naumenko</a>
|
||||
*/
|
||||
public class DiffRow {
|
||||
private Tag tag;
|
||||
private String oldLine;
|
||||
private String newLine;
|
||||
|
||||
public DiffRow(Tag tag, String oldLine, String newLine) {
|
||||
this.tag = tag;
|
||||
this.oldLine = oldLine;
|
||||
this.newLine = newLine;
|
||||
}
|
||||
|
||||
public enum Tag {
|
||||
INSERT, DELETE, CHANGE, EQUAL
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the tag
|
||||
*/
|
||||
public Tag getTag() {
|
||||
return tag;
|
||||
}
|
||||
/**
|
||||
* @param tag the tag to set
|
||||
*/
|
||||
public void setTag(Tag tag) {
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the oldLine
|
||||
*/
|
||||
public String getOldLine() {
|
||||
return oldLine;
|
||||
}
|
||||
/**
|
||||
* @param oldLine the oldLine to set
|
||||
*/
|
||||
public void setOldLine(String oldLine) {
|
||||
this.oldLine = oldLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the newLine
|
||||
*/
|
||||
public String getNewLine() {
|
||||
return newLine;
|
||||
}
|
||||
/**
|
||||
* @param newLine the newLine to set
|
||||
*/
|
||||
public void setNewLine(String newLine) {
|
||||
this.newLine = newLine;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((newLine == null) ? 0 : newLine.hashCode());
|
||||
result = prime * result + ((oldLine == null) ? 0 : oldLine.hashCode());
|
||||
result = prime * result + ((tag == null) ? 0 : tag.hashCode());
|
||||
return result;
|
||||
}
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
DiffRow other = (DiffRow) obj;
|
||||
if (newLine == null) {
|
||||
if (other.newLine != null)
|
||||
return false;
|
||||
} else if (!newLine.equals(other.newLine))
|
||||
return false;
|
||||
if (oldLine == null) {
|
||||
if (other.oldLine != null)
|
||||
return false;
|
||||
} else if (!oldLine.equals(other.oldLine))
|
||||
return false;
|
||||
if (tag == null) {
|
||||
if (other.tag != null)
|
||||
return false;
|
||||
} else if (!tag.equals(other.tag))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "[" + this.tag + "," + this.oldLine + "," + this.newLine + "]";
|
||||
}
|
||||
private Tag tag;
|
||||
private String oldLine;
|
||||
private String newLine;
|
||||
|
||||
public DiffRow(Tag tag, String oldLine, String newLine) {
|
||||
this.tag = tag;
|
||||
this.oldLine = oldLine;
|
||||
this.newLine = newLine;
|
||||
}
|
||||
|
||||
public enum Tag {
|
||||
INSERT, DELETE, CHANGE, EQUAL
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the tag
|
||||
*/
|
||||
public Tag getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tag the tag to set
|
||||
*/
|
||||
public void setTag(Tag tag) {
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the oldLine
|
||||
*/
|
||||
public String getOldLine() {
|
||||
return oldLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param oldLine the oldLine to set
|
||||
*/
|
||||
public void setOldLine(String oldLine) {
|
||||
this.oldLine = oldLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the newLine
|
||||
*/
|
||||
public String getNewLine() {
|
||||
return newLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newLine the newLine to set
|
||||
*/
|
||||
public void setNewLine(String newLine) {
|
||||
this.newLine = newLine;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((newLine == null) ? 0 : newLine.hashCode());
|
||||
result = prime * result + ((oldLine == null) ? 0 : oldLine.hashCode());
|
||||
result = prime * result + ((tag == null) ? 0 : tag.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
DiffRow other = (DiffRow) obj;
|
||||
if (newLine == null) {
|
||||
if (other.newLine != null)
|
||||
return false;
|
||||
} else if (!newLine.equals(other.newLine))
|
||||
return false;
|
||||
if (oldLine == null) {
|
||||
if (other.oldLine != null)
|
||||
return false;
|
||||
} else if (!oldLine.equals(other.oldLine))
|
||||
return false;
|
||||
if (tag == null) {
|
||||
if (other.tag != null)
|
||||
return false;
|
||||
} else if (!tag.equals(other.tag))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "[" + this.tag + "," + this.oldLine + "," + this.newLine + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Java Diff Utills Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
*/
|
||||
package difflib;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -48,358 +48,357 @@ import difflib.DiffRow.Tag;
|
||||
* @author <a href="dm.naumenko@gmail.com">Dmitry Naumenko</a>
|
||||
*/
|
||||
public class DiffRowGenerator {
|
||||
private final boolean showInlineDiffs;
|
||||
private final boolean ignoreWhiteSpaces;
|
||||
private final boolean ignoreBlankLines;
|
||||
private final String InlineOldTag;
|
||||
private final String InlineNewTag;
|
||||
private final String InlineOldCssClass;
|
||||
private final String InlineNewCssClass;
|
||||
private final int columnWidth;
|
||||
|
||||
/**
|
||||
* This class used for building the DiffRowGenerator.
|
||||
* @author dmitry
|
||||
*
|
||||
*/
|
||||
public static class Builder {
|
||||
private boolean showInlineDiffs = false;
|
||||
private boolean ignoreWhiteSpaces = true;
|
||||
private boolean ignoreBlankLines = true;
|
||||
private String InlineOldTag = "span";
|
||||
private String InlineNewTag = "span";
|
||||
private String InlineOldCssClass = "editOldInline";
|
||||
private String InlineNewCssClass = "editNewInline";
|
||||
private int columnWidth = 80;
|
||||
|
||||
/**
|
||||
* Show inline diffs in generating diff rows or not.
|
||||
* @param val the value to set. Default: false.
|
||||
* @return builder with configured showInlineDiff parameter
|
||||
*/
|
||||
public Builder showInlineDiffs(boolean val) {
|
||||
showInlineDiffs = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore white spaces in generating diff rows or not.
|
||||
* @param val the value to set. Default: true.
|
||||
* @return builder with configured ignoreWhiteSpaces parameter
|
||||
*/
|
||||
public Builder ignoreWhiteSpaces(boolean val) {
|
||||
ignoreWhiteSpaces = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore blank lines in generating diff rows or not.
|
||||
* @param val the value to set. Default: true.
|
||||
* @return builder with configured ignoreBlankLines parameter
|
||||
*/
|
||||
public Builder ignoreBlankLines(boolean val) {
|
||||
ignoreBlankLines = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tag used for displaying changes in the original text.
|
||||
* @param tag the tag to set. Without angle brackets. Default: span.
|
||||
* @return builder with configured ignoreBlankLines parameter
|
||||
*/
|
||||
public Builder InlineOldTag(String tag) {
|
||||
InlineOldTag = tag;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tag used for displaying changes in the revised text.
|
||||
* @param tag the tag to set. Without angle brackets. Default: span.
|
||||
* @return builder with configured ignoreBlankLines parameter
|
||||
*/
|
||||
public Builder InlineNewTag(String tag) {
|
||||
InlineNewTag = tag;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the css class used for displaying changes in the original text.
|
||||
* @param cssClass the tag to set. Without any quotes, just word. Default: editOldInline.
|
||||
* @return builder with configured ignoreBlankLines parameter
|
||||
*/
|
||||
public Builder InlineOldCssClass(String cssClass) {
|
||||
InlineOldCssClass = cssClass;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the css class used for displaying changes in the revised text.
|
||||
* @param cssClass the tag to set. Without any quotes, just word. Default: editNewInline.
|
||||
* @return builder with configured ignoreBlankLines parameter
|
||||
*/
|
||||
public Builder InlineNewCssClass(String cssClass) {
|
||||
InlineNewCssClass = cssClass;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the column with of generated lines of original and revised texts.
|
||||
* @param width the width to set. Making it < 0 doesn't have any sense. Default 80.
|
||||
* @return builder with configured ignoreBlankLines parameter
|
||||
*/
|
||||
public Builder columnWidth(int width) {
|
||||
if (width > 0) {
|
||||
columnWidth = width;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the DiffRowGenerator. If some parameters is not set, the default values are used.
|
||||
* @return the customized DiffRowGenerator
|
||||
*/
|
||||
public DiffRowGenerator build() {
|
||||
return new DiffRowGenerator(this);
|
||||
}
|
||||
}
|
||||
|
||||
private DiffRowGenerator(Builder builder) {
|
||||
showInlineDiffs = builder.showInlineDiffs;
|
||||
ignoreWhiteSpaces = builder.ignoreWhiteSpaces; //
|
||||
ignoreBlankLines = builder.ignoreBlankLines; //
|
||||
InlineOldTag = builder.InlineOldTag;
|
||||
InlineNewTag = builder.InlineNewTag;
|
||||
InlineOldCssClass = builder.InlineOldCssClass;
|
||||
InlineNewCssClass = builder.InlineNewCssClass;
|
||||
columnWidth = builder.columnWidth; //
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DiffRows describing the difference between original and revised texts using the
|
||||
* given patch. Useful for displaying side-by-side diff.
|
||||
*
|
||||
* @param original the original text
|
||||
* @param revised the revised text
|
||||
* @return the DiffRows between original and revised texts
|
||||
*/
|
||||
public List<DiffRow> generateDiffRows(List<String> original, List<String> revised) {
|
||||
return generateDiffRows(original, revised, DiffUtils.diff(original, revised));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the DiffRows describing the difference between original and revised texts using the
|
||||
* given patch. Useful for displaying side-by-side diff.
|
||||
*
|
||||
* @param original the original text
|
||||
* @param revised the revised text
|
||||
* @param patch the given patch
|
||||
* @return the DiffRows between original and revised texts
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<DiffRow> generateDiffRows(List<String> original, List<String> revised, Patch patch) {
|
||||
// normalize the lines (expand tabs, escape html entities)
|
||||
original = StringUtills.normalize((List<String>)original);
|
||||
revised = StringUtills.normalize((List<String>)revised);
|
||||
|
||||
// wrap to the column width
|
||||
original = StringUtills.wrapText((List<String>)original, this.columnWidth);
|
||||
revised = StringUtills.wrapText((List<String>)revised, this.columnWidth);
|
||||
|
||||
List<DiffRow> diffRows = new ArrayList<DiffRow>();
|
||||
int endPos = 0;
|
||||
for (int i = 0; i < patch.getDeltas().size(); i++) {
|
||||
Delta delta = patch.getDelta(i);
|
||||
Chunk orig = delta.getOriginal();
|
||||
Chunk rev = delta.getRevised();
|
||||
|
||||
// We should normalize and wrap lines in deltas too.
|
||||
orig.setLines(StringUtills.normalize((List<String>)orig.getLines()));
|
||||
rev.setLines(StringUtills.normalize((List<String>)rev.getLines()));
|
||||
|
||||
orig.setLines(StringUtills.wrapText((List<String>)orig.getLines(), this.columnWidth));
|
||||
rev.setLines(StringUtills.wrapText((List<String>)rev.getLines(), this.columnWidth));
|
||||
|
||||
// catch the equal prefix for each chunk
|
||||
for (String line: original.subList(endPos, orig.getPosition())) {
|
||||
diffRows.add(new DiffRow(Tag.EQUAL, line, line));
|
||||
}
|
||||
|
||||
// Inserted DiffRow
|
||||
if (delta.getClass().equals(InsertDelta.class)) {
|
||||
endPos = orig.last() + 1;
|
||||
for (String line: (List<String>)rev.getLines()) {
|
||||
diffRows.add(new DiffRow(Tag.INSERT, "", line));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Deleted DiffRow
|
||||
if (delta.getClass().equals(DeleteDelta.class)) {
|
||||
endPos = orig.last() + 1;
|
||||
for (String line: (List<String>)orig.getLines()) {
|
||||
diffRows.add(new DiffRow(Tag.DELETE, line, ""));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (showInlineDiffs) {
|
||||
addInlineDiffs(delta);
|
||||
}
|
||||
// the changed size is match
|
||||
if (orig.getSize() == rev.getSize()) {
|
||||
for (int j = 0; j < orig.getSize(); j++) {
|
||||
diffRows.add(new DiffRow(Tag.CHANGE, (String)orig.getLines().get(j),
|
||||
(String)rev.getLines().get(j)));
|
||||
}
|
||||
} else if (orig.getSize() > rev.getSize()) {
|
||||
for (int j = 0; j < orig.getSize(); j++) {
|
||||
diffRows.add(new DiffRow(Tag.CHANGE, (String)orig.getLines().get(j),
|
||||
rev.getLines().size() > j ? (String)rev.getLines().get(j) : ""));
|
||||
}
|
||||
} else {
|
||||
for (int j = 0; j < rev.getSize(); j++) {
|
||||
diffRows.add(new DiffRow(Tag.CHANGE, orig.getLines().size() > j ?
|
||||
(String)orig.getLines().get(j) : "", (String)rev.getLines().get(j)));
|
||||
}
|
||||
}
|
||||
endPos = orig.last() + 1;
|
||||
}
|
||||
|
||||
// Copy the final matching chunk if any.
|
||||
for (String line: original.subList(endPos, original.size())) {
|
||||
diffRows.add(new DiffRow(Tag.EQUAL, line, line));
|
||||
}
|
||||
return diffRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the inline diffs for given delta
|
||||
* @param delta the given delta
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void addInlineDiffs(Delta delta) {
|
||||
List<String> orig = (List<String>)delta.getOriginal().getLines();
|
||||
List<String> rev = (List<String>)delta.getRevised().getLines();
|
||||
LinkedList<String> origList = new LinkedList<String>();
|
||||
for (Character character: join(orig, "\n").toCharArray()) {
|
||||
origList.add(character.toString());
|
||||
}
|
||||
LinkedList<String> revList = new LinkedList<String>();
|
||||
for (Character character: join(rev, "\n").toCharArray()) {
|
||||
revList.add(character.toString());
|
||||
}
|
||||
List<Delta> inlineDeltas = DiffUtils.diff(origList, revList).getDeltas();
|
||||
if (inlineDeltas.size() < 3) {
|
||||
Collections.reverse(inlineDeltas);
|
||||
for (Delta inlineDelta: inlineDeltas) {
|
||||
Chunk inlineOrig = inlineDelta.getOriginal();
|
||||
Chunk inlineRev = inlineDelta.getRevised();
|
||||
if (inlineDelta.getClass().equals(DeleteDelta.class)) {
|
||||
origList = wrapInTag(origList, inlineOrig.getPosition(),
|
||||
inlineOrig.getPosition() + inlineOrig.getSize() + 1,
|
||||
this.InlineOldTag, this.InlineOldCssClass);
|
||||
} else if (inlineDelta.getClass().equals(InsertDelta.class)) {
|
||||
revList = wrapInTag(revList, inlineRev.getPosition(),
|
||||
inlineRev.getPosition() + inlineRev.getSize() + 1,
|
||||
this.InlineNewTag, this.InlineNewCssClass);
|
||||
} else if (inlineDelta.getClass().equals(ChangeDelta.class)) {
|
||||
origList = wrapInTag(origList, inlineOrig.getPosition(),
|
||||
inlineOrig.getPosition() + inlineOrig.getSize() + 1,
|
||||
this.InlineOldTag, this.InlineOldCssClass);
|
||||
revList = wrapInTag(revList, inlineRev.getPosition(),
|
||||
inlineRev.getPosition() + inlineRev.getSize() + 1,
|
||||
this.InlineNewTag, this.InlineNewCssClass);
|
||||
}
|
||||
}
|
||||
StringBuilder origResult = new StringBuilder(), revResult = new StringBuilder();
|
||||
for (String character: origList) {
|
||||
origResult.append(character);
|
||||
}
|
||||
for (String character: revList) {
|
||||
revResult.append(character);
|
||||
}
|
||||
delta.getOriginal().setLines(Arrays.asList(origResult.toString().split("\n")));
|
||||
delta.getRevised().setLines(Arrays.asList(revResult.toString().split("\n")));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the elements in the sequence with the given tag
|
||||
* @param startPosition the position from which tag should start. The counting start from a zero.
|
||||
* @param endPosition the position before which tag should should be closed.
|
||||
* @param tag the tag name without angle brackets, just a word
|
||||
* @param cssClass the optional css class
|
||||
*/
|
||||
public static LinkedList<String> wrapInTag(LinkedList<String> sequence, int startPosition, int endPosition, String tag, String cssClass) {
|
||||
LinkedList<String> result = (LinkedList<String>)sequence.clone();
|
||||
StringBuilder tagBuilder = new StringBuilder();
|
||||
tagBuilder.append("<");
|
||||
tagBuilder.append(tag);
|
||||
if (cssClass != null) {
|
||||
tagBuilder.append(" class=\"");
|
||||
tagBuilder.append(cssClass);
|
||||
tagBuilder.append("\"");
|
||||
}
|
||||
tagBuilder.append(">");
|
||||
String startTag = tagBuilder.toString();
|
||||
|
||||
tagBuilder.delete(0, tagBuilder.length());
|
||||
|
||||
tagBuilder.append("</");
|
||||
tagBuilder.append(tag);
|
||||
tagBuilder.append(">");
|
||||
String endTag = tagBuilder.toString();
|
||||
|
||||
result.add(startPosition, startTag);
|
||||
result.add(endPosition, endTag);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the given line with the given tag
|
||||
* @param line the given line
|
||||
* @param tag the tag name without angle brackets, just a word
|
||||
* @param cssClass the optional css class
|
||||
* @return the wrapped string
|
||||
*/
|
||||
public static String wrapInTag(String line, String tag, String cssClass) {
|
||||
StringBuilder tagBuilder = new StringBuilder();
|
||||
tagBuilder.append("<");
|
||||
tagBuilder.append(tag);
|
||||
if (cssClass != null) {
|
||||
tagBuilder.append(" class=\"");
|
||||
tagBuilder.append(cssClass);
|
||||
tagBuilder.append("\"");
|
||||
}
|
||||
tagBuilder.append(">");
|
||||
String startTag = tagBuilder.toString();
|
||||
|
||||
tagBuilder.delete(0, tagBuilder.length());
|
||||
|
||||
tagBuilder.append("</");
|
||||
tagBuilder.append(tag);
|
||||
tagBuilder.append(">");
|
||||
String endTag = tagBuilder.toString();
|
||||
|
||||
return startTag + line + endTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* The helper method for joining collections
|
||||
* @param <T>
|
||||
* @param objs the collection to join
|
||||
* @param delimiter the delimiter to use
|
||||
* @return the joined string
|
||||
*/
|
||||
private static <T> String join(final Iterable<T> objs, final String delimiter) {
|
||||
Iterator<T> iter = objs.iterator();
|
||||
if (!iter.hasNext()) {
|
||||
return "";
|
||||
}
|
||||
StringBuffer buffer = new StringBuffer(String.valueOf(iter.next()));
|
||||
while (iter.hasNext()) {
|
||||
buffer.append(delimiter).append(String.valueOf(iter.next()));
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
private final boolean showInlineDiffs;
|
||||
private final boolean ignoreWhiteSpaces;
|
||||
private final boolean ignoreBlankLines;
|
||||
private final String InlineOldTag;
|
||||
private final String InlineNewTag;
|
||||
private final String InlineOldCssClass;
|
||||
private final String InlineNewCssClass;
|
||||
private final int columnWidth;
|
||||
|
||||
/**
|
||||
* This class used for building the DiffRowGenerator.
|
||||
* @author dmitry
|
||||
*
|
||||
*/
|
||||
public static class Builder {
|
||||
private boolean showInlineDiffs = false;
|
||||
private boolean ignoreWhiteSpaces = true;
|
||||
private boolean ignoreBlankLines = true;
|
||||
private String InlineOldTag = "span";
|
||||
private String InlineNewTag = "span";
|
||||
private String InlineOldCssClass = "editOldInline";
|
||||
private String InlineNewCssClass = "editNewInline";
|
||||
private int columnWidth = 80;
|
||||
|
||||
/**
|
||||
* Show inline diffs in generating diff rows or not.
|
||||
* @param val the value to set. Default: false.
|
||||
* @return builder with configured showInlineDiff parameter
|
||||
*/
|
||||
public Builder showInlineDiffs(boolean val) {
|
||||
showInlineDiffs = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore white spaces in generating diff rows or not.
|
||||
* @param val the value to set. Default: true.
|
||||
* @return builder with configured ignoreWhiteSpaces parameter
|
||||
*/
|
||||
public Builder ignoreWhiteSpaces(boolean val) {
|
||||
ignoreWhiteSpaces = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore blank lines in generating diff rows or not.
|
||||
* @param val the value to set. Default: true.
|
||||
* @return builder with configured ignoreBlankLines parameter
|
||||
*/
|
||||
public Builder ignoreBlankLines(boolean val) {
|
||||
ignoreBlankLines = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tag used for displaying changes in the original text.
|
||||
* @param tag the tag to set. Without angle brackets. Default: span.
|
||||
* @return builder with configured ignoreBlankLines parameter
|
||||
*/
|
||||
public Builder InlineOldTag(String tag) {
|
||||
InlineOldTag = tag;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tag used for displaying changes in the revised text.
|
||||
* @param tag the tag to set. Without angle brackets. Default: span.
|
||||
* @return builder with configured ignoreBlankLines parameter
|
||||
*/
|
||||
public Builder InlineNewTag(String tag) {
|
||||
InlineNewTag = tag;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the css class used for displaying changes in the original text.
|
||||
* @param cssClass the tag to set. Without any quotes, just word. Default: editOldInline.
|
||||
* @return builder with configured ignoreBlankLines parameter
|
||||
*/
|
||||
public Builder InlineOldCssClass(String cssClass) {
|
||||
InlineOldCssClass = cssClass;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the css class used for displaying changes in the revised text.
|
||||
* @param cssClass the tag to set. Without any quotes, just word. Default: editNewInline.
|
||||
* @return builder with configured ignoreBlankLines parameter
|
||||
*/
|
||||
public Builder InlineNewCssClass(String cssClass) {
|
||||
InlineNewCssClass = cssClass;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the column with of generated lines of original and revised texts.
|
||||
* @param width the width to set. Making it < 0 doesn't have any sense. Default 80.
|
||||
* @return builder with configured ignoreBlankLines parameter
|
||||
*/
|
||||
public Builder columnWidth(int width) {
|
||||
if (width > 0) {
|
||||
columnWidth = width;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the DiffRowGenerator. If some parameters is not set, the default values are used.
|
||||
* @return the customized DiffRowGenerator
|
||||
*/
|
||||
public DiffRowGenerator build() {
|
||||
return new DiffRowGenerator(this);
|
||||
}
|
||||
}
|
||||
|
||||
private DiffRowGenerator(Builder builder) {
|
||||
showInlineDiffs = builder.showInlineDiffs;
|
||||
ignoreWhiteSpaces = builder.ignoreWhiteSpaces; //
|
||||
ignoreBlankLines = builder.ignoreBlankLines; //
|
||||
InlineOldTag = builder.InlineOldTag;
|
||||
InlineNewTag = builder.InlineNewTag;
|
||||
InlineOldCssClass = builder.InlineOldCssClass;
|
||||
InlineNewCssClass = builder.InlineNewCssClass;
|
||||
columnWidth = builder.columnWidth; //
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DiffRows describing the difference between original and revised texts using the
|
||||
* given patch. Useful for displaying side-by-side diff.
|
||||
*
|
||||
* @param original the original text
|
||||
* @param revised the revised text
|
||||
* @return the DiffRows between original and revised texts
|
||||
*/
|
||||
public List<DiffRow> generateDiffRows(List<String> original, List<String> revised) {
|
||||
return generateDiffRows(original, revised, DiffUtils.diff(original, revised));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the DiffRows describing the difference between original and revised texts using the
|
||||
* given patch. Useful for displaying side-by-side diff.
|
||||
*
|
||||
* @param original the original text
|
||||
* @param revised the revised text
|
||||
* @param patch the given patch
|
||||
* @return the DiffRows between original and revised texts
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<DiffRow> generateDiffRows(List<String> original, List<String> revised, Patch patch) {
|
||||
// normalize the lines (expand tabs, escape html entities)
|
||||
original = StringUtills.normalize((List<String>) original);
|
||||
revised = StringUtills.normalize((List<String>) revised);
|
||||
|
||||
// wrap to the column width
|
||||
original = StringUtills.wrapText((List<String>) original, this.columnWidth);
|
||||
revised = StringUtills.wrapText((List<String>) revised, this.columnWidth);
|
||||
|
||||
List<DiffRow> diffRows = new ArrayList<DiffRow>();
|
||||
int endPos = 0;
|
||||
for (int i = 0; i < patch.getDeltas().size(); i++) {
|
||||
Delta delta = patch.getDelta(i);
|
||||
Chunk orig = delta.getOriginal();
|
||||
Chunk rev = delta.getRevised();
|
||||
|
||||
// We should normalize and wrap lines in deltas too.
|
||||
orig.setLines(StringUtills.normalize((List<String>) orig.getLines()));
|
||||
rev.setLines(StringUtills.normalize((List<String>) rev.getLines()));
|
||||
|
||||
orig.setLines(StringUtills.wrapText((List<String>) orig.getLines(), this.columnWidth));
|
||||
rev.setLines(StringUtills.wrapText((List<String>) rev.getLines(), this.columnWidth));
|
||||
|
||||
// catch the equal prefix for each chunk
|
||||
for (String line : original.subList(endPos, orig.getPosition())) {
|
||||
diffRows.add(new DiffRow(Tag.EQUAL, line, line));
|
||||
}
|
||||
|
||||
// Inserted DiffRow
|
||||
if (delta.getClass().equals(InsertDelta.class)) {
|
||||
endPos = orig.last() + 1;
|
||||
for (String line : (List<String>) rev.getLines()) {
|
||||
diffRows.add(new DiffRow(Tag.INSERT, "", line));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Deleted DiffRow
|
||||
if (delta.getClass().equals(DeleteDelta.class)) {
|
||||
endPos = orig.last() + 1;
|
||||
for (String line : (List<String>) orig.getLines()) {
|
||||
diffRows.add(new DiffRow(Tag.DELETE, line, ""));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (showInlineDiffs) {
|
||||
addInlineDiffs(delta);
|
||||
}
|
||||
// the changed size is match
|
||||
if (orig.getSize() == rev.getSize()) {
|
||||
for (int j = 0; j < orig.getSize(); j++) {
|
||||
diffRows.add(new DiffRow(Tag.CHANGE, (String) orig.getLines().get(j),
|
||||
(String) rev.getLines().get(j)));
|
||||
}
|
||||
} else if (orig.getSize() > rev.getSize()) {
|
||||
for (int j = 0; j < orig.getSize(); j++) {
|
||||
diffRows.add(new DiffRow(Tag.CHANGE, (String) orig.getLines().get(j), rev
|
||||
.getLines().size() > j ? (String) rev.getLines().get(j) : ""));
|
||||
}
|
||||
} else {
|
||||
for (int j = 0; j < rev.getSize(); j++) {
|
||||
diffRows.add(new DiffRow(Tag.CHANGE, orig.getLines().size() > j ? (String) orig
|
||||
.getLines().get(j) : "", (String) rev.getLines().get(j)));
|
||||
}
|
||||
}
|
||||
endPos = orig.last() + 1;
|
||||
}
|
||||
|
||||
// Copy the final matching chunk if any.
|
||||
for (String line : original.subList(endPos, original.size())) {
|
||||
diffRows.add(new DiffRow(Tag.EQUAL, line, line));
|
||||
}
|
||||
return diffRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the inline diffs for given delta
|
||||
* @param delta the given delta
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void addInlineDiffs(Delta delta) {
|
||||
List<String> orig = (List<String>) delta.getOriginal().getLines();
|
||||
List<String> rev = (List<String>) delta.getRevised().getLines();
|
||||
LinkedList<String> origList = new LinkedList<String>();
|
||||
for (Character character : join(orig, "\n").toCharArray()) {
|
||||
origList.add(character.toString());
|
||||
}
|
||||
LinkedList<String> revList = new LinkedList<String>();
|
||||
for (Character character : join(rev, "\n").toCharArray()) {
|
||||
revList.add(character.toString());
|
||||
}
|
||||
List<Delta> inlineDeltas = DiffUtils.diff(origList, revList).getDeltas();
|
||||
if (inlineDeltas.size() < 3) {
|
||||
Collections.reverse(inlineDeltas);
|
||||
for (Delta inlineDelta : inlineDeltas) {
|
||||
Chunk inlineOrig = inlineDelta.getOriginal();
|
||||
Chunk inlineRev = inlineDelta.getRevised();
|
||||
if (inlineDelta.getClass().equals(DeleteDelta.class)) {
|
||||
origList = wrapInTag(origList, inlineOrig.getPosition(), inlineOrig
|
||||
.getPosition()
|
||||
+ inlineOrig.getSize() + 1, this.InlineOldTag, this.InlineOldCssClass);
|
||||
} else if (inlineDelta.getClass().equals(InsertDelta.class)) {
|
||||
revList = wrapInTag(revList, inlineRev.getPosition(), inlineRev.getPosition()
|
||||
+ inlineRev.getSize() + 1, this.InlineNewTag, this.InlineNewCssClass);
|
||||
} else if (inlineDelta.getClass().equals(ChangeDelta.class)) {
|
||||
origList = wrapInTag(origList, inlineOrig.getPosition(), inlineOrig
|
||||
.getPosition()
|
||||
+ inlineOrig.getSize() + 1, this.InlineOldTag, this.InlineOldCssClass);
|
||||
revList = wrapInTag(revList, inlineRev.getPosition(), inlineRev.getPosition()
|
||||
+ inlineRev.getSize() + 1, this.InlineNewTag, this.InlineNewCssClass);
|
||||
}
|
||||
}
|
||||
StringBuilder origResult = new StringBuilder(), revResult = new StringBuilder();
|
||||
for (String character : origList) {
|
||||
origResult.append(character);
|
||||
}
|
||||
for (String character : revList) {
|
||||
revResult.append(character);
|
||||
}
|
||||
delta.getOriginal().setLines(Arrays.asList(origResult.toString().split("\n")));
|
||||
delta.getRevised().setLines(Arrays.asList(revResult.toString().split("\n")));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the elements in the sequence with the given tag
|
||||
* @param startPosition the position from which tag should start. The counting start from a zero.
|
||||
* @param endPosition the position before which tag should should be closed.
|
||||
* @param tag the tag name without angle brackets, just a word
|
||||
* @param cssClass the optional css class
|
||||
*/
|
||||
public static LinkedList<String> wrapInTag(LinkedList<String> sequence, int startPosition,
|
||||
int endPosition, String tag, String cssClass) {
|
||||
LinkedList<String> result = (LinkedList<String>) sequence.clone();
|
||||
StringBuilder tagBuilder = new StringBuilder();
|
||||
tagBuilder.append("<");
|
||||
tagBuilder.append(tag);
|
||||
if (cssClass != null) {
|
||||
tagBuilder.append(" class=\"");
|
||||
tagBuilder.append(cssClass);
|
||||
tagBuilder.append("\"");
|
||||
}
|
||||
tagBuilder.append(">");
|
||||
String startTag = tagBuilder.toString();
|
||||
|
||||
tagBuilder.delete(0, tagBuilder.length());
|
||||
|
||||
tagBuilder.append("</");
|
||||
tagBuilder.append(tag);
|
||||
tagBuilder.append(">");
|
||||
String endTag = tagBuilder.toString();
|
||||
|
||||
result.add(startPosition, startTag);
|
||||
result.add(endPosition, endTag);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the given line with the given tag
|
||||
* @param line the given line
|
||||
* @param tag the tag name without angle brackets, just a word
|
||||
* @param cssClass the optional css class
|
||||
* @return the wrapped string
|
||||
*/
|
||||
public static String wrapInTag(String line, String tag, String cssClass) {
|
||||
StringBuilder tagBuilder = new StringBuilder();
|
||||
tagBuilder.append("<");
|
||||
tagBuilder.append(tag);
|
||||
if (cssClass != null) {
|
||||
tagBuilder.append(" class=\"");
|
||||
tagBuilder.append(cssClass);
|
||||
tagBuilder.append("\"");
|
||||
}
|
||||
tagBuilder.append(">");
|
||||
String startTag = tagBuilder.toString();
|
||||
|
||||
tagBuilder.delete(0, tagBuilder.length());
|
||||
|
||||
tagBuilder.append("</");
|
||||
tagBuilder.append(tag);
|
||||
tagBuilder.append(">");
|
||||
String endTag = tagBuilder.toString();
|
||||
|
||||
return startTag + line + endTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* The helper method for joining collections
|
||||
* @param <T>
|
||||
* @param objs the collection to join
|
||||
* @param delimiter the delimiter to use
|
||||
* @return the joined string
|
||||
*/
|
||||
private static <T> String join(final Iterable<T> objs, final String delimiter) {
|
||||
Iterator<T> iter = objs.iterator();
|
||||
if (!iter.hasNext()) {
|
||||
return "";
|
||||
}
|
||||
StringBuffer buffer = new StringBuffer(String.valueOf(iter.next()));
|
||||
while (iter.hasNext()) {
|
||||
buffer.append(delimiter).append(String.valueOf(iter.next()));
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Java Diff Utills Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
*/
|
||||
package difflib;
|
||||
|
||||
import java.util.*;
|
||||
@@ -31,144 +31,313 @@ import difflib.myers.*;
|
||||
* @version 0.4.1
|
||||
*/
|
||||
public class DiffUtils {
|
||||
private static DiffAlgorithm defaultDiffAlgorithm = new MyersDiff();
|
||||
private static Pattern unifiedDiffChunkRe =
|
||||
Pattern.compile("@@\\s+-(?:(\\d+)(?:,(\\d+))?)\\s+\\+(?:(\\d+)(?:,(\\d+))?)\\s+@@");
|
||||
|
||||
/**
|
||||
* Compute the difference between the original and revised texts with default diff algorithm
|
||||
*
|
||||
* @param original the original text
|
||||
* @param revised the revised text
|
||||
* @return the patch describing the difference between the original and revised texts
|
||||
*/
|
||||
public static Patch diff(List<?> original, List<?> revised) {
|
||||
return DiffUtils.diff(original, revised, defaultDiffAlgorithm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the difference between the original and revised texts with given diff algorithm
|
||||
*
|
||||
* @param original the original text
|
||||
* @param revised the revised text
|
||||
* @param algorithm the given algorithm
|
||||
* @return the patch describing the difference between the original and revised texts
|
||||
*/
|
||||
public static Patch diff(List<?> original, List<?> revised, DiffAlgorithm algorithm) {
|
||||
return algorithm.diff(original, revised);
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch the original text with given patch
|
||||
*
|
||||
* @param original the original text
|
||||
* @param patch the given patch
|
||||
* @return the revised text
|
||||
* @throws PatchFailedException if can't apply patch
|
||||
*/
|
||||
public static List<?> patch(List<?> original, Patch patch) throws PatchFailedException {
|
||||
return patch.applyTo(original);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpatch the revised text for a given patch
|
||||
*
|
||||
* @param revised the revised text
|
||||
* @param patch the given patch
|
||||
* @return the original text
|
||||
*/
|
||||
public static List<?> unpatch(List<?> revised, Patch patch) {
|
||||
return patch.restore(revised);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given text in unified format and creates the list of deltas for it.
|
||||
*
|
||||
* @param diff the text in unified format
|
||||
* @return the patch with deltas.
|
||||
*/
|
||||
public static Patch parseUnifiedDiff(List<String> diff) {
|
||||
boolean inPrelude = true;
|
||||
List<Object[]> rawChunk = new ArrayList<Object[]>();
|
||||
Patch patch = new Patch();
|
||||
|
||||
int old_ln = 0, old_n = 0, new_ln = 0, new_n = 0;
|
||||
String tag = "", rest = "";
|
||||
for (String line: diff) {
|
||||
// Skip leading lines until after we've seen one starting with '+++'
|
||||
if (inPrelude) {
|
||||
if (line.startsWith("+++")) {
|
||||
inPrelude = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Matcher m = unifiedDiffChunkRe.matcher(line);
|
||||
if (m.find()) {
|
||||
// Process the lines in the previous chunk
|
||||
if (rawChunk.size() != 0) {
|
||||
List<String> oldChunkLines = new ArrayList<String>();
|
||||
List<String> newChunkLines = new ArrayList<String>();
|
||||
|
||||
for (Object[] raw_line: rawChunk) {
|
||||
tag = (String)raw_line[0];
|
||||
rest = (String)raw_line[1];
|
||||
if (tag.equals(" ") || tag.equals("-")) {
|
||||
oldChunkLines.add(rest);
|
||||
}
|
||||
if (tag.equals(" ") || tag.equals("+")) {
|
||||
newChunkLines.add(rest);
|
||||
}
|
||||
}
|
||||
patch.addDelta(new ChangeDelta(new Chunk(old_ln - 1, old_n, oldChunkLines),
|
||||
new Chunk(new_ln - 1, new_n, newChunkLines)));
|
||||
rawChunk.clear();
|
||||
}
|
||||
// Parse the @@ header
|
||||
old_ln = m.group(1) == null ? 1 : Integer.parseInt(m.group(1));
|
||||
old_n = m.group(2) == null ? 1 : Integer.parseInt(m.group(2));
|
||||
new_ln = m.group(3) == null ? 1 : Integer.parseInt(m.group(3));
|
||||
new_n = m.group(4) == null ? 1 : Integer.parseInt(m.group(4));
|
||||
old_ln = Integer.parseInt(m.group(1));
|
||||
|
||||
if (old_ln == 0) {
|
||||
old_ln += 1;
|
||||
}
|
||||
if (new_ln == 0) {
|
||||
new_ln += 1;
|
||||
}
|
||||
} else {
|
||||
if (line.length() > 0) {
|
||||
tag = line.substring(0, 1);
|
||||
rest = line.substring(1);
|
||||
if (tag.equals(" ") || tag.equals("+") || tag.equals("-")) {
|
||||
rawChunk.add(new Object[] {tag, rest});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process the lines in the last chunk
|
||||
if (rawChunk.size() != 0) {
|
||||
List<String> oldChunkLines = new ArrayList<String>();
|
||||
List<String> newChunkLines = new ArrayList<String>();
|
||||
|
||||
for (Object[] raw_line: rawChunk) {
|
||||
tag = (String)raw_line[0];
|
||||
rest = (String)raw_line[1];
|
||||
if (tag.equals(" ") || tag.equals("-")) {
|
||||
oldChunkLines.add(rest);
|
||||
}
|
||||
if (tag.equals(" ") || tag.equals("+")) {
|
||||
newChunkLines.add(rest);
|
||||
}
|
||||
}
|
||||
|
||||
patch.addDelta(new ChangeDelta(new Chunk(old_ln - 1, old_n, oldChunkLines),
|
||||
new Chunk(new_ln - 1, new_n, newChunkLines)));
|
||||
rawChunk.clear();
|
||||
}
|
||||
|
||||
return patch;
|
||||
}
|
||||
|
||||
private static DiffAlgorithm defaultDiffAlgorithm = new MyersDiff();
|
||||
private static Pattern unifiedDiffChunkRe =
|
||||
Pattern.compile("@@\\s+-(?:(\\d+)(?:,(\\d+))?)\\s+\\+(?:(\\d+)(?:,(\\d+))?)\\s+@@");
|
||||
|
||||
/**
|
||||
* Compute the difference between the original and revised texts with default diff algorithm
|
||||
*
|
||||
* @param original the original text
|
||||
* @param revised the revised text
|
||||
* @return the patch describing the difference between the original and revised texts
|
||||
*/
|
||||
public static Patch diff(List<?> original, List<?> revised) {
|
||||
return DiffUtils.diff(original, revised, defaultDiffAlgorithm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the difference between the original and revised texts with given diff algorithm
|
||||
*
|
||||
* @param original the original text
|
||||
* @param revised the revised text
|
||||
* @param algorithm the given algorithm
|
||||
* @return the patch describing the difference between the original and revised texts
|
||||
*/
|
||||
public static Patch diff(List<?> original, List<?> revised, DiffAlgorithm algorithm) {
|
||||
return algorithm.diff(original, revised);
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch the original text with given patch
|
||||
*
|
||||
* @param original the original text
|
||||
* @param patch the given patch
|
||||
* @return the revised text
|
||||
* @throws PatchFailedException if can't apply patch
|
||||
*/
|
||||
public static List<?> patch(List<?> original, Patch patch) throws PatchFailedException {
|
||||
return patch.applyTo(original);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpatch the revised text for a given patch
|
||||
*
|
||||
* @param revised the revised text
|
||||
* @param patch the given patch
|
||||
* @return the original text
|
||||
*/
|
||||
public static List<?> unpatch(List<?> revised, Patch patch) {
|
||||
return patch.restore(revised);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given text in unified format and creates the list of deltas for it.
|
||||
*
|
||||
* @param diff the text in unified format
|
||||
* @return the patch with deltas.
|
||||
*/
|
||||
public static Patch parseUnifiedDiff(List<String> diff) {
|
||||
boolean inPrelude = true;
|
||||
List<Object[]> rawChunk = new ArrayList<Object[]>();
|
||||
Patch patch = new Patch();
|
||||
|
||||
int old_ln = 0, old_n = 0, new_ln = 0, new_n = 0;
|
||||
String tag = "", rest = "";
|
||||
for (String line : diff) {
|
||||
// Skip leading lines until after we've seen one starting with '+++'
|
||||
if (inPrelude) {
|
||||
if (line.startsWith("+++")) {
|
||||
inPrelude = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Matcher m = unifiedDiffChunkRe.matcher(line);
|
||||
if (m.find()) {
|
||||
// Process the lines in the previous chunk
|
||||
if (rawChunk.size() != 0) {
|
||||
List<String> oldChunkLines = new ArrayList<String>();
|
||||
List<String> newChunkLines = new ArrayList<String>();
|
||||
|
||||
for (Object[] raw_line : rawChunk) {
|
||||
tag = (String) raw_line[0];
|
||||
rest = (String) raw_line[1];
|
||||
if (tag.equals(" ") || tag.equals("-")) {
|
||||
oldChunkLines.add(rest);
|
||||
}
|
||||
if (tag.equals(" ") || tag.equals("+")) {
|
||||
newChunkLines.add(rest);
|
||||
}
|
||||
}
|
||||
patch.addDelta(new ChangeDelta(new Chunk(old_ln - 1, old_n, oldChunkLines),
|
||||
new Chunk(new_ln - 1, new_n, newChunkLines)));
|
||||
rawChunk.clear();
|
||||
}
|
||||
// Parse the @@ header
|
||||
old_ln = m.group(1) == null ? 1 : Integer.parseInt(m.group(1));
|
||||
old_n = m.group(2) == null ? 1 : Integer.parseInt(m.group(2));
|
||||
new_ln = m.group(3) == null ? 1 : Integer.parseInt(m.group(3));
|
||||
new_n = m.group(4) == null ? 1 : Integer.parseInt(m.group(4));
|
||||
old_ln = Integer.parseInt(m.group(1));
|
||||
|
||||
if (old_ln == 0) {
|
||||
old_ln += 1;
|
||||
}
|
||||
if (new_ln == 0) {
|
||||
new_ln += 1;
|
||||
}
|
||||
} else {
|
||||
if (line.length() > 0) {
|
||||
tag = line.substring(0, 1);
|
||||
rest = line.substring(1);
|
||||
if (tag.equals(" ") || tag.equals("+") || tag.equals("-")) {
|
||||
rawChunk.add(new Object[] { tag, rest });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process the lines in the last chunk
|
||||
if (rawChunk.size() != 0) {
|
||||
List<String> oldChunkLines = new ArrayList<String>();
|
||||
List<String> newChunkLines = new ArrayList<String>();
|
||||
|
||||
for (Object[] raw_line : rawChunk) {
|
||||
tag = (String) raw_line[0];
|
||||
rest = (String) raw_line[1];
|
||||
if (tag.equals(" ") || tag.equals("-")) {
|
||||
oldChunkLines.add(rest);
|
||||
}
|
||||
if (tag.equals(" ") || tag.equals("+")) {
|
||||
newChunkLines.add(rest);
|
||||
}
|
||||
}
|
||||
|
||||
patch.addDelta(new ChangeDelta(new Chunk(old_ln - 1, old_n, oldChunkLines), new Chunk(
|
||||
new_ln - 1, new_n, newChunkLines)));
|
||||
rawChunk.clear();
|
||||
}
|
||||
|
||||
return patch;
|
||||
}
|
||||
|
||||
/**
|
||||
* generateUnifiedDiff takes a Patch and some other arguments, returning the Unified Diff format text representing the Patch.
|
||||
* @author Bill James (tankerbay@gmail.com)
|
||||
*
|
||||
* @param original - Filename of the original (unrevised file)
|
||||
* @param revised - Filename of the revised file
|
||||
* @param originalLines - Lines of the original file
|
||||
* @param patch - Patch created by the diff() function
|
||||
* @param contextSize - number of lines of context output around each difference in the file.
|
||||
* @return List of strings representing the Unified Diff representation of the Patch argument.
|
||||
*/
|
||||
public static List<String> generateUnifiedDiff(String original, String revised,
|
||||
List<String> originalLines, Patch patch, int contextSize) {
|
||||
List<String> ret = new ArrayList<String>();
|
||||
ret.add("--- " + original);
|
||||
ret.add("+++ " + revised);
|
||||
|
||||
// Hmm, I thought the Deltas were sorted already... turns out they're not.
|
||||
List<Delta> patchDeltas = new ArrayList<Delta>( patch.getDeltas() );
|
||||
Collections.sort( patchDeltas, new Comparator<Delta>() {
|
||||
public int compare( Delta a, Delta b ) {
|
||||
return new Integer(a.getOriginal().getPosition()).compareTo( b.getOriginal().getPosition() );
|
||||
}
|
||||
});
|
||||
|
||||
// code outside the if block also works for single-delta issues.
|
||||
List<Delta> deltas = new ArrayList<Delta>(); // current list of Delta's to process
|
||||
Delta delta = patchDeltas.get(0);
|
||||
deltas.add(delta); // add the first Delta to the current set
|
||||
// if there's more than 1 Delta, we may need to output them together
|
||||
if (patchDeltas.size() > 1) {
|
||||
for (int i = 1; i < patchDeltas.size(); i++) {
|
||||
int position = delta.getOriginal().getPosition(); // store the current position of
|
||||
// the first Delta
|
||||
|
||||
// Check if the next Delta is too close to the current position.
|
||||
// And if it is, add it to the current set
|
||||
Delta nextDelta = patchDeltas.get(i);
|
||||
if ((position + delta.getOriginal().getSize() + contextSize) >=
|
||||
(nextDelta.getOriginal().getPosition() - contextSize)) {
|
||||
deltas.add(nextDelta);
|
||||
} else {
|
||||
// if it isn't, output the current set,
|
||||
// then create a new set and add the current Delta to it.
|
||||
List<String> curBlock = processDeltas(originalLines, deltas, contextSize);
|
||||
ret.addAll(curBlock);
|
||||
deltas.clear();
|
||||
deltas.add(nextDelta);
|
||||
}
|
||||
delta = nextDelta;
|
||||
}
|
||||
|
||||
}
|
||||
// don't forget to process the last set of Deltas
|
||||
List<String> curBlock = processDeltas(originalLines, deltas, contextSize);
|
||||
ret.addAll(curBlock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* processDeltas takes a list of Deltas and outputs them together in a single block of Unified-Diff-format text.
|
||||
* @author Bill James (tankerbay@gmail.com)
|
||||
*
|
||||
* @param origLines - the lines of the original file
|
||||
* @param deltas - the Deltas to be output as a single block
|
||||
* @param contextSize - the number of lines of context to place around block
|
||||
* @return
|
||||
*/
|
||||
private static List<String> processDeltas(List<String> origLines, List<Delta> deltas,
|
||||
int contextSize) {
|
||||
List<String> buffer = new ArrayList<String>();
|
||||
int origTotal = 0; // counter for total lines output from Original
|
||||
int revTotal = 0; // counter for total lines output from Original
|
||||
int line;
|
||||
|
||||
Delta curDelta = deltas.get(0);
|
||||
|
||||
// NOTE: +1 to overcome the 0-offset Position
|
||||
int origStart = curDelta.getOriginal().getPosition() + 1 - contextSize;
|
||||
if (origStart < 1) {
|
||||
origStart = 1;
|
||||
}
|
||||
|
||||
int revStart = curDelta.getRevised().getPosition() + 1 - contextSize;
|
||||
if (revStart < 1) {
|
||||
revStart = 1;
|
||||
}
|
||||
|
||||
// find the start of the wrapper context code
|
||||
int contextStart = curDelta.getOriginal().getPosition() - contextSize;
|
||||
if (contextStart < 0) {
|
||||
contextStart = 0; // clamp to the start of the file
|
||||
}
|
||||
|
||||
// output the context before the first Delta
|
||||
for (line = contextStart; line < curDelta.getOriginal().getPosition(); line++) { //
|
||||
buffer.add(" " + origLines.get(line));
|
||||
origTotal++;
|
||||
revTotal++;
|
||||
}
|
||||
|
||||
// output the first Delta
|
||||
buffer.addAll(getDeltaText(curDelta));
|
||||
origTotal += curDelta.getOriginal().getLines().size();
|
||||
revTotal += curDelta.getRevised().getLines().size();
|
||||
|
||||
int deltaIndex = 1;
|
||||
while (deltaIndex < deltas.size()) { // for each of the other Deltas
|
||||
Delta nextDelta = deltas.get(deltaIndex);
|
||||
int intermediateStart = curDelta.getOriginal().getPosition()
|
||||
+ curDelta.getOriginal().getLines().size();
|
||||
for (line = intermediateStart; line < nextDelta.getOriginal().getPosition(); line++) {
|
||||
// output the code between the last Delta and this one
|
||||
buffer.add(" " + origLines.get(line));
|
||||
origTotal++;
|
||||
revTotal++;
|
||||
}
|
||||
buffer.addAll(getDeltaText(nextDelta)); // output the Delta
|
||||
origTotal += nextDelta.getOriginal().getLines().size();
|
||||
revTotal += nextDelta.getRevised().getLines().size();
|
||||
curDelta = nextDelta;
|
||||
deltaIndex++;
|
||||
}
|
||||
|
||||
// Now output the post-Delta context code, clamping the end of the file
|
||||
contextStart = curDelta.getOriginal().getPosition()
|
||||
+ curDelta.getOriginal().getLines().size();
|
||||
for (line = contextStart; (line < (contextStart + contextSize))
|
||||
&& (line < origLines.size()); line++) {
|
||||
buffer.add(" " + origLines.get(line));
|
||||
origTotal++;
|
||||
revTotal++;
|
||||
}
|
||||
|
||||
// Create and insert the block header, conforming to the Unified Diff standard
|
||||
StringBuffer header = new StringBuffer();
|
||||
header.append("@@ -");
|
||||
header.append(origStart);
|
||||
header.append(",");
|
||||
header.append(origTotal);
|
||||
header.append(" +");
|
||||
header.append(revStart);
|
||||
header.append(",");
|
||||
header.append(revTotal);
|
||||
header.append(" @@");
|
||||
buffer.add(0, header.toString());
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* getDeltaText returns the lines to be added to the Unified Diff text from the Delta parameter
|
||||
* @author Bill James (tankerbay@gmail.com)
|
||||
*
|
||||
* @param delta - the Delta to output
|
||||
* @return list of String lines of code.
|
||||
*/
|
||||
private static List<String> getDeltaText(Delta delta) {
|
||||
List<String> buffer = new ArrayList<String>();
|
||||
for (Object line : delta.getOriginal().getLines()) {
|
||||
buffer.add("-" + line);
|
||||
}
|
||||
for (Object line : delta.getRevised().getLines()) {
|
||||
buffer.add("+" + line);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Java Diff Utills Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
*/
|
||||
package difflib;
|
||||
|
||||
import java.util.List;
|
||||
@@ -26,52 +26,52 @@ import java.util.List;
|
||||
* @author <a href="dm.naumenko@gmail.com">Dmitry Naumenko</a>
|
||||
*/
|
||||
public class InsertDelta extends Delta {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public InsertDelta(Chunk original, Chunk revised) {
|
||||
super(original, revised);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @throws PatchFailedException
|
||||
*/
|
||||
@Override
|
||||
public void applyTo(List<Object> target) throws PatchFailedException {
|
||||
verify(target);
|
||||
int position = this.getOriginal().getPosition();
|
||||
List<?> lines = this.getRevised().getLines();
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
target.add(position + i, lines.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void restore(List<Object> target) {
|
||||
int position = getRevised().getPosition();
|
||||
int size = getRevised().getSize();
|
||||
for (int i = 0; i < size; i++) {
|
||||
target.remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(List<?> target) throws PatchFailedException {
|
||||
if (getOriginal().getPosition() > target.size()) {
|
||||
throw new PatchFailedException("Incorrect patch for delta: " +
|
||||
"delta original position > target size");
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public InsertDelta(Chunk original, Chunk revised) {
|
||||
super(original, revised);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @throws PatchFailedException
|
||||
*/
|
||||
@Override
|
||||
public void applyTo(List<Object> target) throws PatchFailedException {
|
||||
verify(target);
|
||||
int position = this.getOriginal().getPosition();
|
||||
List<?> lines = this.getRevised().getLines();
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
target.add(position + i, lines.get(i));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[InsertDelta, position: " + getOriginal().getPosition() + ", lines: " +
|
||||
getRevised().getLines() + "]";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void restore(List<Object> target) {
|
||||
int position = getRevised().getPosition();
|
||||
int size = getRevised().getSize();
|
||||
for (int i = 0; i < size; i++) {
|
||||
target.remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(List<?> target) throws PatchFailedException {
|
||||
if (getOriginal().getPosition() > target.size()) {
|
||||
throw new PatchFailedException("Incorrect patch for delta: "
|
||||
+ "delta original position > target size");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[InsertDelta, position: " + getOriginal().getPosition() + ", lines: "
|
||||
+ getRevised().getLines() + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Java Diff Utills Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
*/
|
||||
package difflib;
|
||||
|
||||
import java.util.*;
|
||||
@@ -26,86 +26,85 @@ import java.util.*;
|
||||
* @author <a href="dm.naumenko@gmail.com">Dmitry Naumenko</a>
|
||||
*/
|
||||
public class Patch {
|
||||
private List<Delta> deltas = new LinkedList<Delta>();
|
||||
private boolean isSorted = false;
|
||||
|
||||
/**
|
||||
* Apply this patch to the given target
|
||||
* @param target
|
||||
* @return the patched text
|
||||
* @throws PatchFailedException if can't apply patch
|
||||
*/
|
||||
public List<?> applyTo(List<?> target) throws PatchFailedException {
|
||||
List<Object> result = new LinkedList<Object>(target);
|
||||
ListIterator<Delta> it = getDeltas().listIterator(deltas.size());
|
||||
private List<Delta> deltas = new LinkedList<Delta>();
|
||||
private boolean isSorted = false;
|
||||
|
||||
/**
|
||||
* Apply this patch to the given target
|
||||
* @param target
|
||||
* @return the patched text
|
||||
* @throws PatchFailedException if can't apply patch
|
||||
*/
|
||||
public List<?> applyTo(List<?> target) throws PatchFailedException {
|
||||
List<Object> result = new LinkedList<Object>(target);
|
||||
ListIterator<Delta> it = getDeltas().listIterator(deltas.size());
|
||||
while (it.hasPrevious()) {
|
||||
Delta delta = (Delta) it.previous();
|
||||
delta.applyTo(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the text to original. Opposite to applyTo() method.
|
||||
* @param target the given target
|
||||
* @return the restored text
|
||||
*/
|
||||
public List<?> restore(List<?> target) {
|
||||
List<Object> result = new LinkedList<Object>(target);
|
||||
ListIterator<Delta> it = getDeltas().listIterator(deltas.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the text to original. Opposite to applyTo() method.
|
||||
* @param target the given target
|
||||
* @return the restored text
|
||||
*/
|
||||
public List<?> restore(List<?> target) {
|
||||
List<Object> result = new LinkedList<Object>(target);
|
||||
ListIterator<Delta> it = getDeltas().listIterator(deltas.size());
|
||||
while (it.hasPrevious()) {
|
||||
Delta delta = (Delta) it.previous();
|
||||
delta.restore(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given delta to this patch
|
||||
* @param delta the given delta
|
||||
*/
|
||||
public void addDelta(Delta delta) {
|
||||
deltas.add(delta);
|
||||
isSorted = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param deltas the deltas to set
|
||||
*/
|
||||
public void setDeltas(List<Delta> deltas) {
|
||||
this.deltas = deltas;
|
||||
isSorted = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of computed deltas
|
||||
* @return the deltas
|
||||
*/
|
||||
public List<Delta> getDeltas() {
|
||||
if (!this.isSorted) {
|
||||
Collections.sort(deltas, new Comparator<Delta>() {
|
||||
public int compare(Delta d1, Delta d2) {
|
||||
if (d1.getOriginal().getPosition() > d2.getOriginal().getPosition()) {
|
||||
return 1;
|
||||
} else if (d1.getOriginal().getPosition() > d2.getOriginal().getPosition()) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
isSorted = true;
|
||||
}
|
||||
return deltas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the specific delta from patch deltas
|
||||
* @param index the index of delta
|
||||
* @return the needed delta
|
||||
*/
|
||||
public Delta getDelta(int index) {
|
||||
return deltas.get(index);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given delta to this patch
|
||||
* @param delta the given delta
|
||||
*/
|
||||
public void addDelta(Delta delta) {
|
||||
deltas.add(delta);
|
||||
isSorted = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param deltas the deltas to set
|
||||
*/
|
||||
public void setDeltas(List<Delta> deltas) {
|
||||
this.deltas = deltas;
|
||||
isSorted = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of computed deltas
|
||||
* @return the deltas
|
||||
*/
|
||||
public List<Delta> getDeltas() {
|
||||
if (!this.isSorted) {
|
||||
Collections.sort(deltas, new Comparator<Delta>() {
|
||||
public int compare(Delta d1, Delta d2) {
|
||||
if (d1.getOriginal().getPosition() > d2.getOriginal().getPosition()) {
|
||||
return 1;
|
||||
} else if (d1.getOriginal().getPosition() > d2.getOriginal().getPosition()) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
isSorted = true;
|
||||
}
|
||||
return deltas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the specific delta from patch deltas
|
||||
* @param index the index of delta
|
||||
* @return the needed delta
|
||||
*/
|
||||
public Delta getDelta(int index) {
|
||||
return deltas.get(index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Java Diff Utills Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
*/
|
||||
package difflib;
|
||||
|
||||
/**
|
||||
@@ -24,12 +24,12 @@ package difflib;
|
||||
* @author <a href="mailto:juanco@suigeneris.org">Juanco Anez</a>
|
||||
*/
|
||||
public class PatchFailedException extends DiffException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public PatchFailedException() {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public PatchFailedException() {
|
||||
}
|
||||
|
||||
|
||||
public PatchFailedException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
@@ -5,64 +5,64 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class StringUtills {
|
||||
|
||||
public static <T> String join(final Iterable<T> objs, final String delimiter) {
|
||||
Iterator<T> iter = objs.iterator();
|
||||
if (!iter.hasNext()) {
|
||||
return "";
|
||||
}
|
||||
StringBuffer buffer = new StringBuffer(String.valueOf(iter.next()));
|
||||
while (iter.hasNext()) {
|
||||
buffer.append(delimiter).append(String.valueOf(iter.next()));
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public static String expandTabs(String str) {
|
||||
return str.replace("\t", " ");
|
||||
}
|
||||
|
||||
public static String htmlEntites(String str) {
|
||||
return str.replace("<", "<").replace(">", ">");
|
||||
}
|
||||
|
||||
public static String normalize(String str) {
|
||||
return expandTabs(htmlEntites(str));
|
||||
}
|
||||
|
||||
public static List<String> normalize(List<String> list) {
|
||||
List<String> result = new LinkedList<String>();
|
||||
for (String line: list) {
|
||||
result.add(normalize(line));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<String> wrapText(List<String> list, int columnWidth) {
|
||||
List<String> result = new LinkedList<String>();
|
||||
for (String line: list) {
|
||||
result.add(wrapText(line, columnWidth));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the text with the given column width
|
||||
* @param line the text
|
||||
* @param columnWidth the given column
|
||||
* @return the wrapped text
|
||||
*/
|
||||
public static String wrapText(String line, int columnWidth) {
|
||||
int lenght = line.length();
|
||||
int delimiter = "<br>".length();
|
||||
int widthIndex = columnWidth;
|
||||
|
||||
for (int count = 0; lenght > widthIndex; count++) {
|
||||
line = line.subSequence(0, widthIndex + delimiter * count) + "<br>"
|
||||
+ line.substring(widthIndex + delimiter * count);
|
||||
widthIndex += columnWidth;
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
public static <T> String join(final Iterable<T> objs, final String delimiter) {
|
||||
Iterator<T> iter = objs.iterator();
|
||||
if (!iter.hasNext()) {
|
||||
return "";
|
||||
}
|
||||
StringBuffer buffer = new StringBuffer(String.valueOf(iter.next()));
|
||||
while (iter.hasNext()) {
|
||||
buffer.append(delimiter).append(String.valueOf(iter.next()));
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public static String expandTabs(String str) {
|
||||
return str.replace("\t", " ");
|
||||
}
|
||||
|
||||
public static String htmlEntites(String str) {
|
||||
return str.replace("<", "<").replace(">", ">");
|
||||
}
|
||||
|
||||
public static String normalize(String str) {
|
||||
return expandTabs(htmlEntites(str));
|
||||
}
|
||||
|
||||
public static List<String> normalize(List<String> list) {
|
||||
List<String> result = new LinkedList<String>();
|
||||
for (String line : list) {
|
||||
result.add(normalize(line));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<String> wrapText(List<String> list, int columnWidth) {
|
||||
List<String> result = new LinkedList<String>();
|
||||
for (String line : list) {
|
||||
result.add(wrapText(line, columnWidth));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the text with the given column width
|
||||
* @param line the text
|
||||
* @param columnWidth the given column
|
||||
* @return the wrapped text
|
||||
*/
|
||||
public static String wrapText(String line, int columnWidth) {
|
||||
int lenght = line.length();
|
||||
int delimiter = "<br>".length();
|
||||
int widthIndex = columnWidth;
|
||||
|
||||
for (int count = 0; lenght > widthIndex; count++) {
|
||||
line = line.subSequence(0, widthIndex + delimiter * count) + "<br>"
|
||||
+ line.substring(widthIndex + delimiter * count);
|
||||
widthIndex += columnWidth;
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,18 +64,14 @@ package difflib.myers;
|
||||
*
|
||||
* @author <a href="mailto:juanco@suigeneris.org">Juanco Anez</a>
|
||||
*/
|
||||
public class DiffException extends Exception
|
||||
{
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public DiffException()
|
||||
{
|
||||
public class DiffException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public DiffException() {
|
||||
}
|
||||
|
||||
public DiffException(String msg)
|
||||
{
|
||||
|
||||
public DiffException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,9 +25,7 @@ package difflib.myers;
|
||||
* @author <a href="mailto:juanco@suigeneris.org">Juanco Anez</a>
|
||||
*
|
||||
*/
|
||||
public final class DiffNode
|
||||
extends PathNode
|
||||
{
|
||||
public final class DiffNode extends PathNode {
|
||||
/**
|
||||
* Constructs a DiffNode.
|
||||
* <p>
|
||||
@@ -40,18 +38,16 @@ public final class DiffNode
|
||||
* @param the position in the revised sequence
|
||||
* @param prev the previous node in the path.
|
||||
*/
|
||||
public DiffNode(int i, int j, PathNode prev)
|
||||
{
|
||||
super(i, j, (prev == null ? null : prev.previousSnake()) );
|
||||
public DiffNode(int i, int j, PathNode prev) {
|
||||
super(i, j, (prev == null ? null : prev.previousSnake()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @return false, always
|
||||
*/
|
||||
public boolean isSnake()
|
||||
{
|
||||
public boolean isSnake() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -67,17 +67,13 @@ package difflib.myers;
|
||||
* @see Diff
|
||||
* @see DiffAlgorithm
|
||||
*/
|
||||
public class DifferentiationFailedException extends DiffException
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public DifferentiationFailedException()
|
||||
{
|
||||
public class DifferentiationFailedException extends DiffException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public DifferentiationFailedException() {
|
||||
}
|
||||
|
||||
public DifferentiationFailedException(String msg)
|
||||
{
|
||||
|
||||
public DifferentiationFailedException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,10 +57,16 @@
|
||||
|
||||
package difflib.myers;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.List;
|
||||
|
||||
import difflib.*;
|
||||
import difflib.ChangeDelta;
|
||||
import difflib.Chunk;
|
||||
import difflib.DeleteDelta;
|
||||
import difflib.Delta;
|
||||
import difflib.DiffAlgorithm;
|
||||
import difflib.InsertDelta;
|
||||
import difflib.Patch;
|
||||
|
||||
/**
|
||||
* A clean-room implementation of <a href="http://www.cs.arizona.edu/people/gene/">
|
||||
@@ -71,25 +77,12 @@ import difflib.*;
|
||||
*
|
||||
* @author <a href="mailto:juanco@suigeneris.org">Juanco Anez</a>
|
||||
*/
|
||||
public class MyersDiff
|
||||
implements DiffAlgorithm
|
||||
{
|
||||
public class MyersDiff implements DiffAlgorithm {
|
||||
/**
|
||||
* Constructs an instance of the Myers differencing algorithm.
|
||||
*/
|
||||
public MyersDiff()
|
||||
{
|
||||
public MyersDiff() {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Return empty diff if get the error while procession the difference.
|
||||
*/
|
||||
@Override
|
||||
public Patch diff(List<?> original, List<?> revised) {
|
||||
return diff(original.toArray(), revised.toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
@@ -97,18 +90,27 @@ public class MyersDiff
|
||||
* Return empty diff if get the error while procession the difference.
|
||||
*/
|
||||
@Override
|
||||
public Patch diff(Object[] orig, Object[] rev)
|
||||
{
|
||||
public Patch diff(List<?> original, List<?> revised) {
|
||||
return diff(original.toArray(), revised.toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Return empty diff if get the error while procession the difference.
|
||||
*/
|
||||
@Override
|
||||
public Patch diff(Object[] orig, Object[] rev) {
|
||||
PathNode path;
|
||||
try {
|
||||
path = buildPath(orig, rev);
|
||||
return buildRevision(path, orig, rev);
|
||||
} catch (DifferentiationFailedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
path = buildPath(orig, rev);
|
||||
return buildRevision(path, orig, rev);
|
||||
} catch (DifferentiationFailedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return new Patch();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Computes the minimum diffpath that expresses de differences
|
||||
* between the original and revised sequences, according
|
||||
@@ -120,76 +122,67 @@ public class MyersDiff
|
||||
* @throws DifferentiationFailedException if a diff path could not be found.
|
||||
*/
|
||||
public static PathNode buildPath(Object[] orig, Object[] rev)
|
||||
throws DifferentiationFailedException
|
||||
{
|
||||
throws DifferentiationFailedException {
|
||||
if (orig == null)
|
||||
throw new IllegalArgumentException("original sequence is null");
|
||||
if (rev == null)
|
||||
throw new IllegalArgumentException("revised sequence is null");
|
||||
|
||||
|
||||
// these are local constants
|
||||
final int N = orig.length;
|
||||
final int M = rev.length;
|
||||
|
||||
|
||||
final int MAX = N + M + 1;
|
||||
final int size = 1 + 2 * MAX;
|
||||
final int middle = (size + 1) / 2;
|
||||
final PathNode diagonal[] = new PathNode[size];
|
||||
|
||||
|
||||
diagonal[middle + 1] = new Snake(0, -1, null);
|
||||
for (int d = 0; d < MAX; d++)
|
||||
{
|
||||
for (int k = -d; k <= d; k += 2)
|
||||
{
|
||||
for (int d = 0; d < MAX; d++) {
|
||||
for (int k = -d; k <= d; k += 2) {
|
||||
final int kmiddle = middle + k;
|
||||
final int kplus = kmiddle + 1;
|
||||
final int kminus = kmiddle - 1;
|
||||
PathNode prev = null;
|
||||
|
||||
|
||||
int i;
|
||||
if ( (k == -d) ||
|
||||
(k != d && diagonal[kminus].i < diagonal[kplus].i))
|
||||
{
|
||||
if ((k == -d) || (k != d && diagonal[kminus].i < diagonal[kplus].i)) {
|
||||
i = diagonal[kplus].i;
|
||||
prev = diagonal[kplus];
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
i = diagonal[kminus].i + 1;
|
||||
prev = diagonal[kminus];
|
||||
}
|
||||
|
||||
|
||||
diagonal[kminus] = null; // no longer used
|
||||
|
||||
|
||||
int j = i - k;
|
||||
|
||||
|
||||
PathNode node = new DiffNode(i, j, prev);
|
||||
|
||||
|
||||
// orig and rev are zero-based
|
||||
// but the algorithm is one-based
|
||||
// that's why there's no +1 when indexing the sequences
|
||||
while (i < N && j < M && orig[i].equals(rev[j]))
|
||||
{
|
||||
while (i < N && j < M && orig[i].equals(rev[j])) {
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
if (i > node.i)
|
||||
node = new Snake(i, j, node);
|
||||
|
||||
|
||||
diagonal[kmiddle] = node;
|
||||
|
||||
if (i >= N && j >= M)
|
||||
{
|
||||
|
||||
if (i >= N && j >= M) {
|
||||
return diagonal[kmiddle];
|
||||
}
|
||||
}
|
||||
diagonal[middle+d-1] = null;
|
||||
|
||||
diagonal[middle + d - 1] = null;
|
||||
|
||||
}
|
||||
// According to Myers, this cannot happen
|
||||
throw new DifferentiationFailedException("could not find a diff path");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a {@link Revision} from a difference path.
|
||||
*
|
||||
@@ -200,46 +193,67 @@ public class MyersDiff
|
||||
* @throws DifferentiationFailedException if a {@link Revision} could
|
||||
* not be built from the given path.
|
||||
*/
|
||||
public static Patch buildRevision(PathNode path, Object[] orig, Object[] rev)
|
||||
{
|
||||
public static Patch buildRevision(PathNode path, Object[] orig, Object[] rev) {
|
||||
if (path == null)
|
||||
throw new IllegalArgumentException("path is null");
|
||||
if (orig == null)
|
||||
throw new IllegalArgumentException("original sequence is null");
|
||||
if (rev == null)
|
||||
throw new IllegalArgumentException("revised sequence is null");
|
||||
|
||||
|
||||
Patch patch = new Patch();
|
||||
if (path.isSnake())
|
||||
path = path.prev;
|
||||
while (path != null && path.prev != null && path.prev.j >= 0)
|
||||
{
|
||||
if(path.isSnake())
|
||||
throw new IllegalStateException("bad diffpath: found snake when looking for diff");
|
||||
while (path != null && path.prev != null && path.prev.j >= 0) {
|
||||
if (path.isSnake())
|
||||
throw new IllegalStateException("bad diffpath: found snake when looking for diff");
|
||||
int i = path.i;
|
||||
int j = path.j;
|
||||
|
||||
|
||||
path = path.prev;
|
||||
int ianchor = path.i;
|
||||
int janchor = path.j;
|
||||
|
||||
Chunk original = new Chunk(ianchor, i - ianchor, Arrays.copyOfRange(orig, ianchor, i));
|
||||
Chunk revised = new Chunk(janchor, j - janchor, Arrays.copyOfRange(rev, janchor, j));
|
||||
Chunk original = new Chunk(ianchor, i - ianchor, copyOfRange(orig, ianchor, i));
|
||||
Chunk revised = new Chunk(janchor, j - janchor, copyOfRange(rev, janchor, j));
|
||||
Delta delta = null;
|
||||
if (original.getSize() == 0 && revised.getSize() != 0) {
|
||||
delta = new InsertDelta(original, revised);
|
||||
delta = new InsertDelta(original, revised);
|
||||
} else if (original.getSize() > 0 && revised.getSize() == 0) {
|
||||
delta = new DeleteDelta(original, revised);
|
||||
delta = new DeleteDelta(original, revised);
|
||||
} else {
|
||||
delta = new ChangeDelta(original, revised);
|
||||
delta = new ChangeDelta(original, revised);
|
||||
}
|
||||
|
||||
|
||||
patch.addDelta(delta);
|
||||
if (path.isSnake())
|
||||
path = path.prev;
|
||||
}
|
||||
return patch;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Copied here from JDK 1.6
|
||||
*/
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T[] copyOfRange(T[] original, int from, int to) {
|
||||
return copyOfRange(original, from, to, (Class<T[]>) original.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Copied here from JDK 1.6
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T, U> T[] copyOfRange(U[] original, int from, int to,
|
||||
Class<? extends T[]> newType) {
|
||||
int newLength = to - from;
|
||||
if (newLength < 0)
|
||||
throw new IllegalArgumentException(from + " > " + to);
|
||||
T[] copy = ((Object) newType == (Object) Object[].class) ? (T[]) new Object[newLength]
|
||||
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
|
||||
System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength));
|
||||
return copy;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -67,34 +67,32 @@ package difflib.myers;
|
||||
* @see Snake
|
||||
*
|
||||
*/
|
||||
public abstract class PathNode
|
||||
{
|
||||
public abstract class PathNode {
|
||||
/** Position in the original sequence. */
|
||||
public final int i;
|
||||
/** Position in the revised sequence. */
|
||||
public final int j;
|
||||
/** The previous node in the path. */
|
||||
public final PathNode prev;
|
||||
|
||||
|
||||
/**
|
||||
* Concatenates a new path node with an existing diffpath.
|
||||
* @param i The position in the original sequence for the new node.
|
||||
* @param j The position in the revised sequence for the new node.
|
||||
* @param prev The previous node in the path.
|
||||
*/
|
||||
public PathNode(int i, int j, PathNode prev)
|
||||
{
|
||||
public PathNode(int i, int j, PathNode prev) {
|
||||
this.i = i;
|
||||
this.j = j;
|
||||
this.prev = prev;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is this node a {@link Snake Snake node}?
|
||||
* @return true if this is a {@link Snake Snake node}
|
||||
*/
|
||||
public abstract boolean isSnake();
|
||||
|
||||
|
||||
/**
|
||||
* Is this a bootstrap node?
|
||||
* <p>
|
||||
@@ -102,11 +100,10 @@ public abstract class PathNode
|
||||
* less than zero.
|
||||
* @return tru if this is a bootstrap node.
|
||||
*/
|
||||
public boolean isBootstrap()
|
||||
{
|
||||
public boolean isBootstrap() {
|
||||
return i < 0 || j < 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Skips sequences of {@link DiffNode DiffNodes} until a
|
||||
* {@link Snake} or bootstrap node is found, or the end
|
||||
@@ -115,24 +112,21 @@ public abstract class PathNode
|
||||
* <code>null</code>
|
||||
* if none found.
|
||||
*/
|
||||
public final PathNode previousSnake()
|
||||
{
|
||||
public final PathNode previousSnake() {
|
||||
if (isBootstrap())
|
||||
return null;
|
||||
if (!isSnake() && prev != null)
|
||||
return prev.previousSnake();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer("[");
|
||||
PathNode node = this;
|
||||
while (node != null)
|
||||
{
|
||||
while (node != null) {
|
||||
buf.append("(");
|
||||
buf.append(Integer.toString(node.i));
|
||||
buf.append(",");
|
||||
|
||||
@@ -70,9 +70,7 @@ package difflib.myers;
|
||||
* @author <a href="mailto:juanco@suigeneris.org">Juanco Anez</a>
|
||||
*
|
||||
*/
|
||||
public final class Snake
|
||||
extends PathNode
|
||||
{
|
||||
public final class Snake extends PathNode {
|
||||
/**
|
||||
* Constructs a snake node.
|
||||
*
|
||||
@@ -80,18 +78,16 @@ public final class Snake
|
||||
* @param the position in the revised sequence
|
||||
* @param prev the previous node in the path.
|
||||
*/
|
||||
public Snake(int i, int j, PathNode prev)
|
||||
{
|
||||
public Snake(int i, int j, PathNode prev) {
|
||||
super(i, j, prev);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @return true always
|
||||
*/
|
||||
public boolean isSnake()
|
||||
{
|
||||
public boolean isSnake() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user