diff --git a/atf-application/pom.xml b/atf-application/pom.xml
index db7f6086..e33c4b17 100644
--- a/atf-application/pom.xml
+++ b/atf-application/pom.xml
@@ -283,6 +283,53 @@
+
+
+ org.codehaus.mojo
+ findbugs-maven-plugin
+ 3.0.5
+
+ Max
+
+ medium
+
+ true
+ FindReturnRef,RuntimeExceptionCapture
+
+
+
+ analyze-compile
+ compile
+
+ check
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/atf-application/src/main/java/ru/bsc/test/autotester/diff/Diff.java b/atf-application/src/main/java/ru/bsc/test/autotester/diff/Diff.java
index 5579c353..b37df1ab 100644
--- a/atf-application/src/main/java/ru/bsc/test/autotester/diff/Diff.java
+++ b/atf-application/src/main/java/ru/bsc/test/autotester/diff/Diff.java
@@ -1,97 +1,99 @@
-/*
- * Diff Match and Patch
- * Copyright 2018 The diff-match-patch Authors.
- * https://github.com/google/diff-match-patch
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ru.bsc.test.autotester.diff;
-
-/**
- * Class representing one diff operation.
- */
-@SuppressWarnings("all")
-public class Diff {
- /**
- * One of: INSERT, DELETE or EQUAL.
- */
- public Operation operation;
- /**
- * The text associated with this diff operation.
- */
- public String text;
-
- /**
- * Constructor. Initializes the diff with the provided values.
- * @param operation One of INSERT, DELETE or EQUAL.
- * @param text The text being applied.
- */
- public Diff(Operation operation, String text) {
- // Construct a diff with the specified operation and text.
- this.operation = operation;
- this.text = text;
- }
-
- /**
- * Display a human-readable version of this Diff.
- * @return text version.
- */
- public String toString() {
- String prettyText = this.text.replace('\n', '\u00b6');
- return "Diff(" + this.operation + ",\"" + prettyText + "\")";
- }
-
- /**
- * Create a numeric hash value for a Diff.
- * This function is not used by DMP.
- * @return Hash value.
- */
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = (operation == null) ? 0 : operation.hashCode();
- result += prime * ((text == null) ? 0 : text.hashCode());
- return result;
- }
-
- /**
- * Is this Diff equivalent to another Diff?
- * @param obj Another Diff to compare against.
- * @return true or false.
- */
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- Diff other = (Diff) obj;
- if (operation != other.operation) {
- return false;
- }
- if (text == null) {
- if (other.text != null) {
- return false;
- }
- } else if (!text.equals(other.text)) {
- return false;
- }
- return true;
- }
-}
+/*
+ * Diff Match and Patch
+ * Copyright 2018 The diff-match-patch Authors.
+ * https://github.com/google/diff-match-patch
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ru.bsc.test.autotester.diff;
+
+import java.io.Serializable;
+
+/**
+ * Class representing one diff operation.
+ */
+@SuppressWarnings("all")
+public class Diff implements Serializable {
+ /**
+ * One of: INSERT, DELETE or EQUAL.
+ */
+ public Operation operation;
+ /**
+ * The text associated with this diff operation.
+ */
+ public String text;
+
+ /**
+ * Constructor. Initializes the diff with the provided values.
+ * @param operation One of INSERT, DELETE or EQUAL.
+ * @param text The text being applied.
+ */
+ public Diff(Operation operation, String text) {
+ // Construct a diff with the specified operation and text.
+ this.operation = operation;
+ this.text = text;
+ }
+
+ /**
+ * Display a human-readable version of this Diff.
+ * @return text version.
+ */
+ public String toString() {
+ String prettyText = this.text.replace('\n', '\u00b6');
+ return "Diff(" + this.operation + ",\"" + prettyText + "\")";
+ }
+
+ /**
+ * Create a numeric hash value for a Diff.
+ * This function is not used by DMP.
+ * @return Hash value.
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = (operation == null) ? 0 : operation.hashCode();
+ result += prime * ((text == null) ? 0 : text.hashCode());
+ return result;
+ }
+
+ /**
+ * Is this Diff equivalent to another Diff?
+ * @param obj Another Diff to compare against.
+ * @return true or false.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Diff other = (Diff) obj;
+ if (operation != other.operation) {
+ return false;
+ }
+ if (text == null) {
+ if (other.text != null) {
+ return false;
+ }
+ } else if (!text.equals(other.text)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/atf-application/src/main/java/ru/bsc/test/autotester/diff/DiffMatchPatch.java b/atf-application/src/main/java/ru/bsc/test/autotester/diff/DiffMatchPatch.java
index d09cd8de..1b009245 100644
--- a/atf-application/src/main/java/ru/bsc/test/autotester/diff/DiffMatchPatch.java
+++ b/atf-application/src/main/java/ru/bsc/test/autotester/diff/DiffMatchPatch.java
@@ -1,2313 +1,2315 @@
-/*
- * Diff Match and Patch
- * Copyright 2018 The diff-match-patch Authors.
- * https://github.com/google/diff-match-patch
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ru.bsc.test.autotester.diff;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLDecoder;
-import java.net.URLEncoder;
-import java.util.*;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/*
- * Functions for diff, match and patch.
- * Computes the difference between two texts to create a patch.
- * Applies the patch onto another text, allowing for errors.
- *
- * @author fraser@google.com (Neil Fraser)
- */
-
-/**
- * Class containing the diff, match and patch methods.
- * Also contains the behaviour settings.
- */
-@SuppressWarnings("all")
-public class DiffMatchPatch {
-
- // Defaults.
- // Set these on your diff_match_patch instance to override the defaults.
-
- /**
- * Number of seconds to map a diff before giving up (0 for infinity).
- */
- public float Diff_Timeout = 1.0f;
- /**
- * Cost of an empty edit operation in terms of edit characters.
- */
- public short Diff_EditCost = 4;
- /**
- * At what point is no match declared (0.0 = perfection, 1.0 = very loose).
- */
- public float Match_Threshold = 0.5f;
- /**
- * How far to search for a match (0 = exact location, 1000+ = broad match).
- * A match this many characters away from the expected location will add
- * 1.0 to the score (0.0 is a perfect match).
- */
- public int Match_Distance = 1000;
- /**
- * When deleting a large block of text (over ~64 characters), how close do
- * the contents have to be to match the expected contents. (0.0 = perfection,
- * 1.0 = very loose). Note that Match_Threshold controls how closely the
- * end points of a delete need to match.
- */
- public float Patch_DeleteThreshold = 0.5f;
- /**
- * Chunk size for context length.
- */
- public short Patch_Margin = 4;
-
- /**
- * The number of bits in an int.
- */
- private short Match_MaxBits = 32;
-
-
- // DIFF FUNCTIONS
-
-
- /**
- * Find the differences between two texts.
- * Run a faster, slightly less optimal diff.
- * This method allows the 'checklines' of diffMain() to be optional.
- * Most of the time checklines is wanted, so default to true.
- *
- * @param text1 Old string to be diffed.
- * @param text2 New string to be diffed.
- * @return Linked List of Diff objects.
- */
- public LinkedList diffMain(String text1, String text2) {
- return diffMain(text1, text2, true);
- }
-
- /**
- * Find the differences between two texts.
- *
- * @param text1 Old string to be diffed.
- * @param text2 New string to be diffed.
- * @param checklines Speedup flag. If false, then don't run a
- * line-level diff first to identify the changed areas.
- * If true, then run a faster slightly less optimal diff.
- * @return Linked List of Diff objects.
- */
- public LinkedList diffMain(String text1, String text2,
- boolean checklines) {
- // Set a deadline by which time the diff must be complete.
- long deadline;
- if (Diff_Timeout <= 0) {
- deadline = Long.MAX_VALUE;
- } else {
- deadline = System.currentTimeMillis() + (long) (Diff_Timeout * 1000);
- }
- return diffMain(text1, text2, checklines, deadline);
- }
-
- /**
- * Find the differences between two texts. Simplifies the problem by
- * stripping any common prefix or suffix off the texts before diffing.
- *
- * @param text1 Old string to be diffed.
- * @param text2 New string to be diffed.
- * @param checklines Speedup flag. If false, then don't run a
- * line-level diff first to identify the changed areas.
- * If true, then run a faster slightly less optimal diff.
- * @param deadline Time when the diff should be complete by. Used
- * internally for recursive calls. Users should set DiffTimeout instead.
- * @return Linked List of Diff objects.
- */
- private LinkedList diffMain(String text1, String text2,
- boolean checklines, long deadline) {
- // Check for null inputs.
- if (text1 == null || text2 == null) {
- throw new IllegalArgumentException("Null inputs. (diffMain)");
- }
-
- // Check for equality (speedup).
- LinkedList diffs;
- if (text1.equals(text2)) {
- diffs = new LinkedList();
- if (text1.length() != 0) {
- diffs.add(new Diff(Operation.EQUAL, text1));
- }
- return diffs;
- }
-
- // Trim off common prefix (speedup).
- int commonlength = diffCommonPrefix(text1, text2);
- String commonprefix = text1.substring(0, commonlength);
- text1 = text1.substring(commonlength);
- text2 = text2.substring(commonlength);
-
- // Trim off common suffix (speedup).
- commonlength = diffCommonSuffix(text1, text2);
- String commonsuffix = text1.substring(text1.length() - commonlength);
- text1 = text1.substring(0, text1.length() - commonlength);
- text2 = text2.substring(0, text2.length() - commonlength);
-
- // Compute the diff on the middle block.
- diffs = diffCompute(text1, text2, checklines, deadline);
-
- // Restore the prefix and suffix.
- if (commonprefix.length() != 0) {
- diffs.addFirst(new Diff(Operation.EQUAL, commonprefix));
- }
- if (commonsuffix.length() != 0) {
- diffs.addLast(new Diff(Operation.EQUAL, commonsuffix));
- }
-
- diffCleanupMerge(diffs);
- return diffs;
- }
-
- /**
- * Find the differences between two texts. Assumes that the texts do not
- * have any common prefix or suffix.
- *
- * @param text1 Old string to be diffed.
- * @param text2 New string to be diffed.
- * @param checklines Speedup flag. If false, then don't run a
- * line-level diff first to identify the changed areas.
- * If true, then run a faster slightly less optimal diff.
- * @param deadline Time when the diff should be complete by.
- * @return Linked List of Diff objects.
- */
- private LinkedList diffCompute(String text1, String text2,
- boolean checklines, long deadline) {
- LinkedList diffs = new LinkedList();
-
- if (text1.length() == 0) {
- // Just add some text (speedup).
- diffs.add(new Diff(Operation.INSERT, text2));
- return diffs;
- }
-
- if (text2.length() == 0) {
- // Just delete some text (speedup).
- diffs.add(new Diff(Operation.DELETE, text1));
- return diffs;
- }
-
- String longtext = text1.length() > text2.length() ? text1 : text2;
- String shorttext = text1.length() > text2.length() ? text2 : text1;
- int i = longtext.indexOf(shorttext);
- if (i != -1) {
- // Shorter text is inside the longer text (speedup).
- Operation op = (text1.length() > text2.length()) ?
- Operation.DELETE : Operation.INSERT;
- diffs.add(new Diff(op, longtext.substring(0, i)));
- diffs.add(new Diff(Operation.EQUAL, shorttext));
- diffs.add(new Diff(op, longtext.substring(i + shorttext.length())));
- return diffs;
- }
-
- if (shorttext.length() == 1) {
- // Single character string.
- // After the previous speedup, the character can't be an equality.
- diffs.add(new Diff(Operation.DELETE, text1));
- diffs.add(new Diff(Operation.INSERT, text2));
- return diffs;
- }
-
- // Check to see if the problem can be split in two.
- String[] hm = diffHalfMatch(text1, text2);
- if (hm != null) {
- // A half-match was found, sort out the return data.
- String text1_a = hm[0];
- String text1_b = hm[1];
- String text2_a = hm[2];
- String text2_b = hm[3];
- String mid_common = hm[4];
- // Send both pairs off for separate processing.
- LinkedList diffs_a = diffMain(text1_a, text2_a,
- checklines, deadline);
- LinkedList diffs_b = diffMain(text1_b, text2_b,
- checklines, deadline);
- // Merge the results.
- diffs = diffs_a;
- diffs.add(new Diff(Operation.EQUAL, mid_common));
- diffs.addAll(diffs_b);
- return diffs;
- }
-
- if (checklines && text1.length() > 100 && text2.length() > 100) {
- return diffLineMode(text1, text2, deadline);
- }
-
- return diffBisect(text1, text2, deadline);
- }
-
- /**
- * Do a quick line-level diff on both strings, then rediff the parts for
- * greater accuracy.
- * This speedup can produce non-minimal diffs.
- *
- * @param text1 Old string to be diffed.
- * @param text2 New string to be diffed.
- * @param deadline Time when the diff should be complete by.
- * @return Linked List of Diff objects.
- */
- private LinkedList diffLineMode(String text1, String text2,
- long deadline) {
- // Scan the text on a line-by-line basis first.
- LinesToCharsResult b = diffLinesToChars(text1, text2);
- text1 = b.chars1;
- text2 = b.chars2;
- List linearray = b.lineArray;
-
- LinkedList diffs = diffMain(text1, text2, false, deadline);
-
- // Convert the diff back to original text.
- diffCharsToLines(diffs, linearray);
- // Eliminate freak matches (e.g. blank lines)
- diffCleanupSemantic(diffs);
-
- // Rediff any replacement blocks, this time character-by-character.
- // Add a dummy entry at the end.
- diffs.add(new Diff(Operation.EQUAL, ""));
- int count_delete = 0;
- int count_insert = 0;
- String text_delete = "";
- String text_insert = "";
- ListIterator pointer = diffs.listIterator();
- Diff thisDiff = pointer.next();
- while (thisDiff != null) {
- switch (thisDiff.operation) {
- case INSERT:
- count_insert++;
- text_insert += thisDiff.text;
- break;
- case DELETE:
- count_delete++;
- text_delete += thisDiff.text;
- break;
- case EQUAL:
- // Upon reaching an equality, check for prior redundancies.
- if (count_delete >= 1 && count_insert >= 1) {
- // Delete the offending records and add the merged ones.
- pointer.previous();
- for (int j = 0; j < count_delete + count_insert; j++) {
- pointer.previous();
- pointer.remove();
- }
- for (Diff newDiff : diffMain(text_delete, text_insert, false,
- deadline)) {
- pointer.add(newDiff);
- }
- }
- count_insert = 0;
- count_delete = 0;
- text_delete = "";
- text_insert = "";
- break;
- }
- thisDiff = pointer.hasNext() ? pointer.next() : null;
- }
- diffs.removeLast(); // Remove the dummy entry at the end.
-
- return diffs;
- }
-
- /**
- * Find the 'middle snake' of a diff, split the problem in two
- * and return the recursively constructed diff.
- * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
- *
- * @param text1 Old string to be diffed.
- * @param text2 New string to be diffed.
- * @param deadline Time at which to bail if not yet complete.
- * @return LinkedList of Diff objects.
- */
- protected LinkedList diffBisect(String text1, String text2,
- long deadline) {
- // Cache the text lengths to prevent multiple calls.
- int text1_length = text1.length();
- int text2_length = text2.length();
- int max_d = (text1_length + text2_length + 1) / 2;
- int v_offset = max_d;
- int v_length = 2 * max_d;
- int[] v1 = new int[v_length];
- int[] v2 = new int[v_length];
- for (int x = 0; x < v_length; x++) {
- v1[x] = -1;
- v2[x] = -1;
- }
- v1[v_offset + 1] = 0;
- v2[v_offset + 1] = 0;
- int delta = text1_length - text2_length;
- // If the total number of characters is odd, then the front path will
- // collide with the reverse path.
- boolean front = (delta % 2 != 0);
- // Offsets for start and end of k loop.
- // Prevents mapping of space beyond the grid.
- int k1start = 0;
- int k1end = 0;
- int k2start = 0;
- int k2end = 0;
- for (int d = 0; d < max_d; d++) {
- // Bail out if deadline is reached.
- if (System.currentTimeMillis() > deadline) {
- break;
- }
-
- // Walk the front path one step.
- for (int k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
- int k1_offset = v_offset + k1;
- int x1;
- if (k1 == -d || (k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1])) {
- x1 = v1[k1_offset + 1];
- } else {
- x1 = v1[k1_offset - 1] + 1;
- }
- int y1 = x1 - k1;
- while (x1 < text1_length && y1 < text2_length
- && text1.charAt(x1) == text2.charAt(y1)) {
- x1++;
- y1++;
- }
- v1[k1_offset] = x1;
- if (x1 > text1_length) {
- // Ran off the right of the graph.
- k1end += 2;
- } else if (y1 > text2_length) {
- // Ran off the bottom of the graph.
- k1start += 2;
- } else if (front) {
- int k2_offset = v_offset + delta - k1;
- if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) {
- // Mirror x2 onto top-left coordinate system.
- int x2 = text1_length - v2[k2_offset];
- if (x1 >= x2) {
- // Overlap detected.
- return diffBisectSplit(text1, text2, x1, y1, deadline);
- }
- }
- }
- }
-
- // Walk the reverse path one step.
- for (int k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
- int k2_offset = v_offset + k2;
- int x2;
- if (k2 == -d || (k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1])) {
- x2 = v2[k2_offset + 1];
- } else {
- x2 = v2[k2_offset - 1] + 1;
- }
- int y2 = x2 - k2;
- while (x2 < text1_length && y2 < text2_length
- && text1.charAt(text1_length - x2 - 1)
- == text2.charAt(text2_length - y2 - 1)) {
- x2++;
- y2++;
- }
- v2[k2_offset] = x2;
- if (x2 > text1_length) {
- // Ran off the left of the graph.
- k2end += 2;
- } else if (y2 > text2_length) {
- // Ran off the top of the graph.
- k2start += 2;
- } else if (!front) {
- int k1_offset = v_offset + delta - k2;
- if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) {
- int x1 = v1[k1_offset];
- int y1 = v_offset + x1 - k1_offset;
- // Mirror x2 onto top-left coordinate system.
- x2 = text1_length - x2;
- if (x1 >= x2) {
- // Overlap detected.
- return diffBisectSplit(text1, text2, x1, y1, deadline);
- }
- }
- }
- }
- }
- // Diff took too long and hit the deadline or
- // number of diffs equals number of characters, no commonality at all.
- LinkedList diffs = new LinkedList();
- diffs.add(new Diff(Operation.DELETE, text1));
- diffs.add(new Diff(Operation.INSERT, text2));
- return diffs;
- }
-
- /**
- * Given the location of the 'middle snake', split the diff in two parts
- * and recurse.
- *
- * @param text1 Old string to be diffed.
- * @param text2 New string to be diffed.
- * @param x Index of split point in text1.
- * @param y Index of split point in text2.
- * @param deadline Time at which to bail if not yet complete.
- * @return LinkedList of Diff objects.
- */
- private LinkedList diffBisectSplit(String text1, String text2,
- int x, int y, long deadline) {
- String text1a = text1.substring(0, x);
- String text2a = text2.substring(0, y);
- String text1b = text1.substring(x);
- String text2b = text2.substring(y);
-
- // Compute both diffs serially.
- LinkedList diffs = diffMain(text1a, text2a, false, deadline);
- LinkedList diffsb = diffMain(text1b, text2b, false, deadline);
-
- diffs.addAll(diffsb);
- return diffs;
- }
-
- /**
- * Split two texts into a list of strings. Reduce the texts to a string of
- * hashes where each Unicode character represents one line.
- *
- * @param text1 First string.
- * @param text2 Second string.
- * @return An object containing the encoded text1, the encoded text2 and
- * the List of unique strings. The zeroth element of the List of
- * unique strings is intentionally blank.
- */
- protected LinesToCharsResult diffLinesToChars(String text1, String text2) {
- List lineArray = new ArrayList();
- Map lineHash = new HashMap();
- // e.g. linearray[4] == "Hello\n"
- // e.g. linehash.get("Hello\n") == 4
-
- // "\x00" is a valid character, but various debuggers don't like it.
- // So we'll insert a junk entry to avoid generating a null character.
- lineArray.add("");
-
- String chars1 = diffLinesToCharsMunge(text1, lineArray, lineHash);
- String chars2 = diffLinesToCharsMunge(text2, lineArray, lineHash);
- return new LinesToCharsResult(chars1, chars2, lineArray);
- }
-
- /**
- * Split a text into a list of strings. Reduce the texts to a string of
- * hashes where each Unicode character represents one line.
- *
- * @param text String to encode.
- * @param lineArray List of unique strings.
- * @param lineHash Map of strings to indices.
- * @return Encoded string.
- */
- private String diffLinesToCharsMunge(String text, List lineArray,
- Map lineHash) {
- int lineStart = 0;
- int lineEnd = -1;
- String line;
- StringBuilder chars = new StringBuilder();
- // Walk the text, pulling out a substring for each line.
- // text.split('\n') would would temporarily double our memory footprint.
- // Modifying text would create many large strings to garbage collect.
- while (lineEnd < text.length() - 1) {
- lineEnd = text.indexOf('\n', lineStart);
- if (lineEnd == -1) {
- lineEnd = text.length() - 1;
- }
- line = text.substring(lineStart, lineEnd + 1);
- lineStart = lineEnd + 1;
-
- if (lineHash.containsKey(line)) {
- chars.append(String.valueOf((char) (int) lineHash.get(line)));
- } else {
- lineArray.add(line);
- lineHash.put(line, lineArray.size() - 1);
- chars.append(String.valueOf((char) (lineArray.size() - 1)));
- }
- }
- return chars.toString();
- }
-
- /**
- * Rehydrate the text in a diff from a string of line hashes to real lines of
- * text.
- *
- * @param diffs LinkedList of Diff objects.
- * @param lineArray List of unique strings.
- */
- protected void diffCharsToLines(LinkedList diffs,
- List lineArray) {
- StringBuilder text;
- for (Diff diff : diffs) {
- text = new StringBuilder();
- for (int y = 0; y < diff.text.length(); y++) {
- text.append(lineArray.get(diff.text.charAt(y)));
- }
- diff.text = text.toString();
- }
- }
-
- /**
- * Determine the common prefix of two strings
- *
- * @param text1 First string.
- * @param text2 Second string.
- * @return The number of characters common to the start of each string.
- */
- public int diffCommonPrefix(String text1, String text2) {
- // Performance analysis: http://neil.fraser.name/news/2007/10/09/
- int n = Math.min(text1.length(), text2.length());
- for (int i = 0; i < n; i++) {
- if (text1.charAt(i) != text2.charAt(i)) {
- return i;
- }
- }
- return n;
- }
-
- /**
- * Determine the common suffix of two strings
- *
- * @param text1 First string.
- * @param text2 Second string.
- * @return The number of characters common to the end of each string.
- */
- public int diffCommonSuffix(String text1, String text2) {
- // Performance analysis: http://neil.fraser.name/news/2007/10/09/
- int text1_length = text1.length();
- int text2_length = text2.length();
- int n = Math.min(text1_length, text2_length);
- for (int i = 1; i <= n; i++) {
- if (text1.charAt(text1_length - i) != text2.charAt(text2_length - i)) {
- return i - 1;
- }
- }
- return n;
- }
-
- /**
- * Determine if the suffix of one string is the prefix of another.
- *
- * @param text1 First string.
- * @param text2 Second string.
- * @return The number of characters common to the end of the first
- * string and the start of the second string.
- */
- protected int diffCommonOverlap(String text1, String text2) {
- // Cache the text lengths to prevent multiple calls.
- int text1_length = text1.length();
- int text2_length = text2.length();
- // Eliminate the null case.
- if (text1_length == 0 || text2_length == 0) {
- return 0;
- }
- // Truncate the longer string.
- if (text1_length > text2_length) {
- text1 = text1.substring(text1_length - text2_length);
- } else if (text1_length < text2_length) {
- text2 = text2.substring(0, text1_length);
- }
- int text_length = Math.min(text1_length, text2_length);
- // Quick check for the worst case.
- if (text1.equals(text2)) {
- return text_length;
- }
-
- // Start by looking for a single character match
- // and increase length until no match is found.
- // Performance analysis: http://neil.fraser.name/news/2010/11/04/
- int best = 0;
- int length = 1;
- while (true) {
- String pattern = text1.substring(text_length - length);
- int found = text2.indexOf(pattern);
- if (found == -1) {
- return best;
- }
- length += found;
- if (found == 0 || text1.substring(text_length - length).equals(
- text2.substring(0, length))) {
- best = length;
- length++;
- }
- }
- }
-
- /**
- * Do the two texts share a substring which is at least half the length of
- * the longer text?
- * This speedup can produce non-minimal diffs.
- *
- * @param text1 First string.
- * @param text2 Second string.
- * @return Five element String array, containing the prefix of text1, the
- * suffix of text1, the prefix of text2, the suffix of text2 and the
- * common middle. Or null if there was no match.
- */
- protected String[] diffHalfMatch(String text1, String text2) {
- if (Diff_Timeout <= 0) {
- // Don't risk returning a non-optimal diff if we have unlimited time.
- return null;
- }
- String longtext = text1.length() > text2.length() ? text1 : text2;
- String shorttext = text1.length() > text2.length() ? text2 : text1;
- if (longtext.length() < 4 || shorttext.length() * 2 < longtext.length()) {
- return null; // Pointless.
- }
-
- // First check if the second quarter is the seed for a half-match.
- String[] hm1 = diffHalfMatchI(longtext, shorttext,
- (longtext.length() + 3) / 4);
- // Check again based on the third quarter.
- String[] hm2 = diffHalfMatchI(longtext, shorttext,
- (longtext.length() + 1) / 2);
- String[] hm;
- if (hm1 == null && hm2 == null) {
- return null;
- } else if (hm2 == null) {
- hm = hm1;
- } else if (hm1 == null) {
- hm = hm2;
- } else {
- // Both matched. Select the longest.
- hm = hm1[4].length() > hm2[4].length() ? hm1 : hm2;
- }
-
- // A half-match was found, sort out the return data.
- if (text1.length() > text2.length()) {
- return hm;
- //return new String[]{hm[0], hm[1], hm[2], hm[3], hm[4]};
- } else {
- return new String[]{hm[2], hm[3], hm[0], hm[1], hm[4]};
- }
- }
-
- /**
- * Does a substring of shorttext exist within longtext such that the
- * substring is at least half the length of longtext?
- *
- * @param longtext Longer string.
- * @param shorttext Shorter string.
- * @param i Start index of quarter length substring within longtext.
- * @return Five element String array, containing the prefix of longtext, the
- * suffix of longtext, the prefix of shorttext, the suffix of shorttext
- * and the common middle. Or null if there was no match.
- */
- private String[] diffHalfMatchI(String longtext, String shorttext, int i) {
- // Start with a 1/4 length substring at position i as a seed.
- String seed = longtext.substring(i, i + longtext.length() / 4);
- int j = -1;
- String best_common = "";
- String best_longtext_a = "", best_longtext_b = "";
- String best_shorttext_a = "", best_shorttext_b = "";
- while ((j = shorttext.indexOf(seed, j + 1)) != -1) {
- int prefixLength = diffCommonPrefix(longtext.substring(i),
- shorttext.substring(j));
- int suffixLength = diffCommonSuffix(longtext.substring(0, i),
- shorttext.substring(0, j));
- if (best_common.length() < suffixLength + prefixLength) {
- best_common = shorttext.substring(j - suffixLength, j)
- + shorttext.substring(j, j + prefixLength);
- best_longtext_a = longtext.substring(0, i - suffixLength);
- best_longtext_b = longtext.substring(i + prefixLength);
- best_shorttext_a = shorttext.substring(0, j - suffixLength);
- best_shorttext_b = shorttext.substring(j + prefixLength);
- }
- }
- if (best_common.length() * 2 >= longtext.length()) {
- return new String[]{best_longtext_a, best_longtext_b,
- best_shorttext_a, best_shorttext_b, best_common};
- } else {
- return null;
- }
- }
-
- /**
- * Reduce the number of edits by eliminating semantically trivial equalities.
- *
- * @param diffs LinkedList of Diff objects.
- */
- public void diffCleanupSemantic(LinkedList diffs) {
- if (diffs.isEmpty()) {
- return;
- }
- boolean changes = false;
- Stack equalities = new Stack(); // Stack of qualities.
- String lastequality = null; // Always equal to equalities.lastElement().text
- ListIterator pointer = diffs.listIterator();
- // Number of characters that changed prior to the equality.
- int length_insertions1 = 0;
- int length_deletions1 = 0;
- // Number of characters that changed after the equality.
- int length_insertions2 = 0;
- int length_deletions2 = 0;
- Diff thisDiff = pointer.next();
- while (thisDiff != null) {
- if (thisDiff.operation == Operation.EQUAL) {
- // Equality found.
- equalities.push(thisDiff);
- length_insertions1 = length_insertions2;
- length_deletions1 = length_deletions2;
- length_insertions2 = 0;
- length_deletions2 = 0;
- lastequality = thisDiff.text;
- } else {
- // An insertion or deletion.
- if (thisDiff.operation == Operation.INSERT) {
- length_insertions2 += thisDiff.text.length();
- } else {
- length_deletions2 += thisDiff.text.length();
- }
- // Eliminate an equality that is smaller or equal to the edits on both
- // sides of it.
- if (lastequality != null && (lastequality.length()
- <= Math.max(length_insertions1, length_deletions1))
- && (lastequality.length()
- <= Math.max(length_insertions2, length_deletions2))) {
- //System.out.println("Splitting: '" + lastequality + "'");
- // Walk back to offending equality.
- while (thisDiff != equalities.lastElement()) {
- thisDiff = pointer.previous();
- }
- pointer.next();
-
- // Replace equality with a delete.
- pointer.set(new Diff(Operation.DELETE, lastequality));
- // Insert a corresponding an insert.
- pointer.add(new Diff(Operation.INSERT, lastequality));
-
- equalities.pop(); // Throw away the equality we just deleted.
- if (!equalities.empty()) {
- // Throw away the previous equality (it needs to be reevaluated).
- equalities.pop();
- }
- if (equalities.empty()) {
- // There are no previous equalities, walk back to the start.
- while (pointer.hasPrevious()) {
- pointer.previous();
- }
- } else {
- // There is a safe equality we can fall back to.
- thisDiff = equalities.lastElement();
- while (thisDiff != pointer.previous()) {
- // Intentionally empty loop.
- }
- }
-
- length_insertions1 = 0; // Reset the counters.
- length_insertions2 = 0;
- length_deletions1 = 0;
- length_deletions2 = 0;
- lastequality = null;
- changes = true;
- }
- }
- thisDiff = pointer.hasNext() ? pointer.next() : null;
- }
-
- // Normalize the diff.
- if (changes) {
- diffCleanupMerge(diffs);
- }
- diffCleanupSemanticLossless(diffs);
-
- // Find any overlaps between deletions and insertions.
- // e.g: abcxxxxxxdef
- // -> abcxxxdef
- // e.g: xxxabcdefxxx
- // -> defxxxabc
- // Only extract an overlap if it is as big as the edit ahead or behind it.
- pointer = diffs.listIterator();
- Diff prevDiff = null;
- thisDiff = null;
- if (pointer.hasNext()) {
- prevDiff = pointer.next();
- if (pointer.hasNext()) {
- thisDiff = pointer.next();
- }
- }
- while (thisDiff != null) {
- if (prevDiff.operation == Operation.DELETE &&
- thisDiff.operation == Operation.INSERT) {
- String deletion = prevDiff.text;
- String insertion = thisDiff.text;
- int overlap_length1 = this.diffCommonOverlap(deletion, insertion);
- int overlap_length2 = this.diffCommonOverlap(insertion, deletion);
- if (overlap_length1 >= overlap_length2) {
- if (overlap_length1 >= deletion.length() / 2.0 ||
- overlap_length1 >= insertion.length() / 2.0) {
- // Overlap found. Insert an equality and trim the surrounding edits.
- pointer.previous();
- pointer.add(new Diff(Operation.EQUAL,
- insertion.substring(0, overlap_length1)));
- prevDiff.text =
- deletion.substring(0, deletion.length() - overlap_length1);
- thisDiff.text = insertion.substring(overlap_length1);
- // pointer.add inserts the element before the cursor, so there is
- // no need to step past the new element.
- }
- } else {
- if (overlap_length2 >= deletion.length() / 2.0 ||
- overlap_length2 >= insertion.length() / 2.0) {
- // Reverse overlap found.
- // Insert an equality and swap and trim the surrounding edits.
- pointer.previous();
- pointer.add(new Diff(Operation.EQUAL,
- deletion.substring(0, overlap_length2)));
- prevDiff.operation = Operation.INSERT;
- prevDiff.text =
- insertion.substring(0, insertion.length() - overlap_length2);
- thisDiff.operation = Operation.DELETE;
- thisDiff.text = deletion.substring(overlap_length2);
- // pointer.add inserts the element before the cursor, so there is
- // no need to step past the new element.
- }
- }
- thisDiff = pointer.hasNext() ? pointer.next() : null;
- }
- prevDiff = thisDiff;
- thisDiff = pointer.hasNext() ? pointer.next() : null;
- }
- }
-
- /**
- * Look for single edits surrounded on both sides by equalities
- * which can be shifted sideways to align the edit to a word boundary.
- * e.g: The cat came. -> The cat came.
- *
- * @param diffs LinkedList of Diff objects.
- */
- public void diffCleanupSemanticLossless(LinkedList diffs) {
- String equality1, edit, equality2;
- String commonString;
- int commonOffset;
- int score, bestScore;
- String bestEquality1, bestEdit, bestEquality2;
- // Create a new iterator at the start.
- ListIterator pointer = diffs.listIterator();
- Diff prevDiff = pointer.hasNext() ? pointer.next() : null;
- Diff thisDiff = pointer.hasNext() ? pointer.next() : null;
- Diff nextDiff = pointer.hasNext() ? pointer.next() : null;
- // Intentionally ignore the first and last element (don't need checking).
- while (nextDiff != null) {
- if (prevDiff.operation == Operation.EQUAL &&
- nextDiff.operation == Operation.EQUAL) {
- // This is a single edit surrounded by equalities.
- equality1 = prevDiff.text;
- edit = thisDiff.text;
- equality2 = nextDiff.text;
-
- // First, shift the edit as far left as possible.
- commonOffset = diffCommonSuffix(equality1, edit);
- if (commonOffset != 0) {
- commonString = edit.substring(edit.length() - commonOffset);
- equality1 = equality1.substring(0, equality1.length() - commonOffset);
- edit = commonString + edit.substring(0, edit.length() - commonOffset);
- equality2 = commonString + equality2;
- }
-
- // Second, step character by character right, looking for the best fit.
- bestEquality1 = equality1;
- bestEdit = edit;
- bestEquality2 = equality2;
- bestScore = diffCleanupSemanticScore(equality1, edit)
- + diffCleanupSemanticScore(edit, equality2);
- while (edit.length() != 0 && equality2.length() != 0
- && edit.charAt(0) == equality2.charAt(0)) {
- equality1 += edit.charAt(0);
- edit = edit.substring(1) + equality2.charAt(0);
- equality2 = equality2.substring(1);
- score = diffCleanupSemanticScore(equality1, edit)
- + diffCleanupSemanticScore(edit, equality2);
- // The >= encourages trailing rather than leading whitespace on edits.
- if (score >= bestScore) {
- bestScore = score;
- bestEquality1 = equality1;
- bestEdit = edit;
- bestEquality2 = equality2;
- }
- }
-
- if (!prevDiff.text.equals(bestEquality1)) {
- // We have an improvement, save it back to the diff.
- if (bestEquality1.length() != 0) {
- prevDiff.text = bestEquality1;
- } else {
- pointer.previous(); // Walk past nextDiff.
- pointer.previous(); // Walk past thisDiff.
- pointer.previous(); // Walk past prevDiff.
- pointer.remove(); // Delete prevDiff.
- pointer.next(); // Walk past thisDiff.
- pointer.next(); // Walk past nextDiff.
- }
- thisDiff.text = bestEdit;
- if (bestEquality2.length() != 0) {
- nextDiff.text = bestEquality2;
- } else {
- pointer.remove(); // Delete nextDiff.
- nextDiff = thisDiff;
- thisDiff = prevDiff;
- }
- }
- }
- prevDiff = thisDiff;
- thisDiff = nextDiff;
- nextDiff = pointer.hasNext() ? pointer.next() : null;
- }
- }
-
- /**
- * Given two strings, compute a score representing whether the internal
- * boundary falls on logical boundaries.
- * Scores range from 6 (best) to 0 (worst).
- *
- * @param one First string.
- * @param two Second string.
- * @return The score.
- */
- private int diffCleanupSemanticScore(String one, String two) {
- if (one.length() == 0 || two.length() == 0) {
- // Edges are the best.
- return 6;
- }
-
- // Each port of this function behaves slightly differently due to
- // subtle differences in each language's definition of things like
- // 'whitespace'. Since this function's purpose is largely cosmetic,
- // the choice has been made to use each language's native features
- // rather than force total conformity.
- char char1 = one.charAt(one.length() - 1);
- char char2 = two.charAt(0);
- boolean nonAlphaNumeric1 = !Character.isLetterOrDigit(char1);
- boolean nonAlphaNumeric2 = !Character.isLetterOrDigit(char2);
- boolean whitespace1 = nonAlphaNumeric1 && Character.isWhitespace(char1);
- boolean whitespace2 = nonAlphaNumeric2 && Character.isWhitespace(char2);
- boolean lineBreak1 = whitespace1
- && Character.getType(char1) == Character.CONTROL;
- boolean lineBreak2 = whitespace2
- && Character.getType(char2) == Character.CONTROL;
- boolean blankLine1 = lineBreak1 && BLANKLINEEND.matcher(one).find();
- boolean blankLine2 = lineBreak2 && BLANKLINESTART.matcher(two).find();
-
- if (blankLine1 || blankLine2) {
- // Five points for blank lines.
- return 5;
- } else if (lineBreak1 || lineBreak2) {
- // Four points for line breaks.
- return 4;
- } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) {
- // Three points for end of sentences.
- return 3;
- } else if (whitespace1 || whitespace2) {
- // Two points for whitespace.
- return 2;
- } else if (nonAlphaNumeric1 || nonAlphaNumeric2) {
- // One point for non-alphanumeric.
- return 1;
- }
- return 0;
- }
-
- // Define some regex patterns for matching boundaries.
- private Pattern BLANKLINEEND
- = Pattern.compile("\\n\\r?\\n\\Z", Pattern.DOTALL);
- private Pattern BLANKLINESTART
- = Pattern.compile("\\A\\r?\\n\\r?\\n", Pattern.DOTALL);
-
- /**
- * Reduce the number of edits by eliminating operationally trivial equalities.
- *
- * @param diffs LinkedList of Diff objects.
- */
- public void diffCleanupEfficiency(LinkedList diffs) {
- if (diffs.isEmpty()) {
- return;
- }
- boolean changes = false;
- Stack equalities = new Stack(); // Stack of equalities.
- String lastequality = null; // Always equal to equalities.lastElement().text
- ListIterator pointer = diffs.listIterator();
- // Is there an insertion operation before the last equality.
- boolean pre_ins = false;
- // Is there a deletion operation before the last equality.
- boolean pre_del = false;
- // Is there an insertion operation after the last equality.
- boolean post_ins = false;
- // Is there a deletion operation after the last equality.
- boolean post_del = false;
- Diff thisDiff = pointer.next();
- Diff safeDiff = thisDiff; // The last Diff that is known to be unsplitable.
- while (thisDiff != null) {
- if (thisDiff.operation == Operation.EQUAL) {
- // Equality found.
- if (thisDiff.text.length() < Diff_EditCost && (post_ins || post_del)) {
- // Candidate found.
- equalities.push(thisDiff);
- pre_ins = post_ins;
- pre_del = post_del;
- lastequality = thisDiff.text;
- } else {
- // Not a candidate, and can never become one.
- equalities.clear();
- lastequality = null;
- safeDiff = thisDiff;
- }
- post_ins = post_del = false;
- } else {
- // An insertion or deletion.
- if (thisDiff.operation == Operation.DELETE) {
- post_del = true;
- } else {
- post_ins = true;
- }
- /*
- * Five types to be split:
- * ABXYCD
- * AXCD
- * ABXC
- * AXCD
- * ABXC
- */
- if (lastequality != null
- && ((pre_ins && pre_del && post_ins && post_del)
- || ((lastequality.length() < Diff_EditCost / 2)
- && ((pre_ins ? 1 : 0) + (pre_del ? 1 : 0)
- + (post_ins ? 1 : 0) + (post_del ? 1 : 0)) == 3))) {
- //System.out.println("Splitting: '" + lastequality + "'");
- // Walk back to offending equality.
- while (thisDiff != equalities.lastElement()) {
- thisDiff = pointer.previous();
- }
- pointer.next();
-
- // Replace equality with a delete.
- pointer.set(new Diff(Operation.DELETE, lastequality));
- // Insert a corresponding an insert.
- pointer.add(thisDiff = new Diff(Operation.INSERT, lastequality));
-
- equalities.pop(); // Throw away the equality we just deleted.
- lastequality = null;
- if (pre_ins && pre_del) {
- // No changes made which could affect previous entry, keep going.
- post_ins = post_del = true;
- equalities.clear();
- safeDiff = thisDiff;
- } else {
- if (!equalities.empty()) {
- // Throw away the previous equality (it needs to be reevaluated).
- equalities.pop();
- }
- if (equalities.empty()) {
- // There are no previous questionable equalities,
- // walk back to the last known safe diff.
- thisDiff = safeDiff;
- } else {
- // There is an equality we can fall back to.
- thisDiff = equalities.lastElement();
- }
- while (thisDiff != pointer.previous()) {
- // Intentionally empty loop.
- }
- post_ins = post_del = false;
- }
-
- changes = true;
- }
- }
- thisDiff = pointer.hasNext() ? pointer.next() : null;
- }
-
- if (changes) {
- diffCleanupMerge(diffs);
- }
- }
-
- /**
- * Reorder and merge like edit sections. Merge equalities.
- * Any edit section can move as long as it doesn't cross an equality.
- *
- * @param diffs LinkedList of Diff objects.
- */
- public void diffCleanupMerge(LinkedList diffs) {
- diffs.add(new Diff(Operation.EQUAL, "")); // Add a dummy entry at the end.
- ListIterator pointer = diffs.listIterator();
- int count_delete = 0;
- int count_insert = 0;
- String text_delete = "";
- String text_insert = "";
- Diff thisDiff = pointer.next();
- Diff prevEqual = null;
- int commonlength;
- while (thisDiff != null) {
- switch (thisDiff.operation) {
- case INSERT:
- count_insert++;
- text_insert += thisDiff.text;
- prevEqual = null;
- break;
- case DELETE:
- count_delete++;
- text_delete += thisDiff.text;
- prevEqual = null;
- break;
- case EQUAL:
- if (count_delete + count_insert > 1) {
- boolean both_types = count_delete != 0 && count_insert != 0;
- // Delete the offending records.
- pointer.previous(); // Reverse direction.
- while (count_delete-- > 0) {
- pointer.previous();
- pointer.remove();
- }
- while (count_insert-- > 0) {
- pointer.previous();
- pointer.remove();
- }
- if (both_types) {
- // Factor out any common prefixies.
- commonlength = diffCommonPrefix(text_insert, text_delete);
- if (commonlength != 0) {
- if (pointer.hasPrevious()) {
- thisDiff = pointer.previous();
- assert thisDiff.operation == Operation.EQUAL
- : "Previous diff should have been an equality.";
- thisDiff.text += text_insert.substring(0, commonlength);
- pointer.next();
- } else {
- pointer.add(new Diff(Operation.EQUAL,
- text_insert.substring(0, commonlength)));
- }
- text_insert = text_insert.substring(commonlength);
- text_delete = text_delete.substring(commonlength);
- }
- // Factor out any common suffixies.
- commonlength = diffCommonSuffix(text_insert, text_delete);
- if (commonlength != 0) {
- thisDiff = pointer.next();
- thisDiff.text = text_insert.substring(text_insert.length()
- - commonlength) + thisDiff.text;
- text_insert = text_insert.substring(0, text_insert.length()
- - commonlength);
- text_delete = text_delete.substring(0, text_delete.length()
- - commonlength);
- pointer.previous();
- }
- }
- // Insert the merged records.
- if (text_delete.length() != 0) {
- pointer.add(new Diff(Operation.DELETE, text_delete));
- }
- if (text_insert.length() != 0) {
- pointer.add(new Diff(Operation.INSERT, text_insert));
- }
- // Step forward to the equality.
- thisDiff = pointer.hasNext() ? pointer.next() : null;
- } else if (prevEqual != null) {
- // Merge this equality with the previous one.
- prevEqual.text += thisDiff.text;
- pointer.remove();
- thisDiff = pointer.previous();
- pointer.next(); // Forward direction
- }
- count_insert = 0;
- count_delete = 0;
- text_delete = "";
- text_insert = "";
- prevEqual = thisDiff;
- break;
- }
- thisDiff = pointer.hasNext() ? pointer.next() : null;
- }
- if (diffs.getLast().text.length() == 0) {
- diffs.removeLast(); // Remove the dummy entry at the end.
- }
-
- /*
- * Second pass: look for single edits surrounded on both sides by equalities
- * which can be shifted sideways to eliminate an equality.
- * e.g: ABAC -> ABAC
- */
- boolean changes = false;
- // Create a new iterator at the start.
- // (As opposed to walking the current one back.)
- pointer = diffs.listIterator();
- Diff prevDiff = pointer.hasNext() ? pointer.next() : null;
- thisDiff = pointer.hasNext() ? pointer.next() : null;
- Diff nextDiff = pointer.hasNext() ? pointer.next() : null;
- // Intentionally ignore the first and last element (don't need checking).
- while (nextDiff != null) {
- if (prevDiff.operation == Operation.EQUAL &&
- nextDiff.operation == Operation.EQUAL) {
- // This is a single edit surrounded by equalities.
- if (thisDiff.text.endsWith(prevDiff.text)) {
- // Shift the edit over the previous equality.
- thisDiff.text = prevDiff.text
- + thisDiff.text.substring(0, thisDiff.text.length()
- - prevDiff.text.length());
- nextDiff.text = prevDiff.text + nextDiff.text;
- pointer.previous(); // Walk past nextDiff.
- pointer.previous(); // Walk past thisDiff.
- pointer.previous(); // Walk past prevDiff.
- pointer.remove(); // Delete prevDiff.
- pointer.next(); // Walk past thisDiff.
- thisDiff = pointer.next(); // Walk past nextDiff.
- nextDiff = pointer.hasNext() ? pointer.next() : null;
- changes = true;
- } else if (thisDiff.text.startsWith(nextDiff.text)) {
- // Shift the edit over the next equality.
- prevDiff.text += nextDiff.text;
- thisDiff.text = thisDiff.text.substring(nextDiff.text.length())
- + nextDiff.text;
- pointer.remove(); // Delete nextDiff.
- nextDiff = pointer.hasNext() ? pointer.next() : null;
- changes = true;
- }
- }
- prevDiff = thisDiff;
- thisDiff = nextDiff;
- nextDiff = pointer.hasNext() ? pointer.next() : null;
- }
- // If shifts were made, the diff needs reordering and another shift sweep.
- if (changes) {
- diffCleanupMerge(diffs);
- }
- }
-
- /**
- * loc is a location in text1, compute and return the equivalent location in
- * text2.
- * e.g. "The cat" vs "The big cat", 1->1, 5->8
- *
- * @param diffs LinkedList of Diff objects.
- * @param loc Location within text1.
- * @return Location within text2.
- */
- public int diffXIndex(LinkedList diffs, int loc) {
- int chars1 = 0;
- int chars2 = 0;
- int last_chars1 = 0;
- int last_chars2 = 0;
- Diff lastDiff = null;
- for (Diff aDiff : diffs) {
- if (aDiff.operation != Operation.INSERT) {
- // Equality or deletion.
- chars1 += aDiff.text.length();
- }
- if (aDiff.operation != Operation.DELETE) {
- // Equality or insertion.
- chars2 += aDiff.text.length();
- }
- if (chars1 > loc) {
- // Overshot the location.
- lastDiff = aDiff;
- break;
- }
- last_chars1 = chars1;
- last_chars2 = chars2;
- }
- if (lastDiff != null && lastDiff.operation == Operation.DELETE) {
- // The location was deleted.
- return last_chars2;
- }
- // Add the remaining character length.
- return last_chars2 + (loc - last_chars1);
- }
-
- /**
- * Convert a Diff list into a pretty HTML report.
- *
- * @param diffs LinkedList of Diff objects.
- * @return HTML representation.
- */
- public String diffPrettyHtml(LinkedList diffs) {
- StringBuilder html = new StringBuilder();
- for (Diff aDiff : diffs) {
- String text = aDiff.text.replace("&", "&").replace("<", "<")
- .replace(">", ">").replace("\n", "¶
");
- switch (aDiff.operation) {
- case INSERT:
- html.append("").append(text)
- .append("");
- break;
- case DELETE:
- html.append("").append(text)
- .append("");
- break;
- case EQUAL:
- html.append("").append(text).append("");
- break;
- }
- }
- return html.toString();
- }
-
- /**
- * Compute and return the source text (all equalities and deletions).
- *
- * @param diffs LinkedList of Diff objects.
- * @return Source text.
- */
- public String diffText1(LinkedList diffs) {
- StringBuilder text = new StringBuilder();
- for (Diff aDiff : diffs) {
- if (aDiff.operation != Operation.INSERT) {
- text.append(aDiff.text);
- }
- }
- return text.toString();
- }
-
- /**
- * Compute and return the destination text (all equalities and insertions).
- *
- * @param diffs LinkedList of Diff objects.
- * @return Destination text.
- */
- public String diffText2(LinkedList diffs) {
- StringBuilder text = new StringBuilder();
- for (Diff aDiff : diffs) {
- if (aDiff.operation != Operation.DELETE) {
- text.append(aDiff.text);
- }
- }
- return text.toString();
- }
-
- /**
- * Compute the Levenshtein distance; the number of inserted, deleted or
- * substituted characters.
- *
- * @param diffs LinkedList of Diff objects.
- * @return Number of changes.
- */
- public int diffLevenshtein(LinkedList diffs) {
- int levenshtein = 0;
- int insertions = 0;
- int deletions = 0;
- for (Diff aDiff : diffs) {
- switch (aDiff.operation) {
- case INSERT:
- insertions += aDiff.text.length();
- break;
- case DELETE:
- deletions += aDiff.text.length();
- break;
- case EQUAL:
- // A deletion and an insertion is one substitution.
- levenshtein += Math.max(insertions, deletions);
- insertions = 0;
- deletions = 0;
- break;
- }
- }
- levenshtein += Math.max(insertions, deletions);
- return levenshtein;
- }
-
- /**
- * Crush the diff into an encoded string which describes the operations
- * required to transform text1 into text2.
- * E.g. =3\t-2\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'.
- * Operations are tab-separated. Inserted text is escaped using %xx notation.
- *
- * @param diffs Array of Diff objects.
- * @return Delta text.
- */
- public String diffToDelta(LinkedList diffs) {
- StringBuilder text = new StringBuilder();
- for (Diff aDiff : diffs) {
- switch (aDiff.operation) {
- case INSERT:
- try {
- text.append("+").append(URLEncoder.encode(aDiff.text, "UTF-8")
- .replace('+', ' ')).append("\t");
- } catch (UnsupportedEncodingException e) {
- // Not likely on modern system.
- throw new Error("This system does not support UTF-8.", e);
- }
- break;
- case DELETE:
- text.append("-").append(aDiff.text.length()).append("\t");
- break;
- case EQUAL:
- text.append("=").append(aDiff.text.length()).append("\t");
- break;
- }
- }
- String delta = text.toString();
- if (delta.length() != 0) {
- // Strip off trailing tab character.
- delta = delta.substring(0, delta.length() - 1);
- delta = new Patch().unescapeForEncodeUriCompatability(delta);
- }
- return delta;
- }
-
- /**
- * Given the original text1, and an encoded string which describes the
- * operations required to transform text1 into text2, compute the full diff.
- *
- * @param text1 Source string for the diff.
- * @param delta Delta text.
- * @return Array of Diff objects or null if invalid.
- * @throws IllegalArgumentException If invalid input.
- */
- public LinkedList diffFromDelta(String text1, String delta)
- throws IllegalArgumentException {
- LinkedList diffs = new LinkedList();
- int pointer = 0; // Cursor in text1
- String[] tokens = delta.split("\t");
- for (String token : tokens) {
- if (token.length() == 0) {
- // Blank tokens are ok (from a trailing \t).
- continue;
- }
- // Each token begins with a one character parameter which specifies the
- // operation of this token (delete, insert, equality).
- String param = token.substring(1);
- switch (token.charAt(0)) {
- case '+':
- // decode would change all "+" to " "
- param = param.replace("+", "%2B");
- try {
- param = URLDecoder.decode(param, "UTF-8");
- } catch (UnsupportedEncodingException e) {
- // Not likely on modern system.
- throw new Error("This system does not support UTF-8.", e);
- } catch (IllegalArgumentException e) {
- // Malformed URI sequence.
- throw new IllegalArgumentException(
- "Illegal escape in diffFromDelta: " + param, e);
- }
- diffs.add(new Diff(Operation.INSERT, param));
- break;
- case '-':
- // Fall through.
- case '=':
- int n;
- try {
- n = Integer.parseInt(param);
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException(
- "Invalid number in diffFromDelta: " + param, e);
- }
- if (n < 0) {
- throw new IllegalArgumentException(
- "Negative number in diffFromDelta: " + param);
- }
- String text;
- try {
- text = text1.substring(pointer, pointer += n);
- } catch (StringIndexOutOfBoundsException e) {
- throw new IllegalArgumentException("Delta length (" + pointer
- + ") larger than source text length (" + text1.length()
- + ").", e);
- }
- if (token.charAt(0) == '=') {
- diffs.add(new Diff(Operation.EQUAL, text));
- } else {
- diffs.add(new Diff(Operation.DELETE, text));
- }
- break;
- default:
- // Anything else is an error.
- throw new IllegalArgumentException(
- "Invalid diff operation in diffFromDelta: " + token.charAt(0));
- }
- }
- if (pointer != text1.length()) {
- throw new IllegalArgumentException("Delta length (" + pointer
- + ") smaller than source text length (" + text1.length() + ").");
- }
- return diffs;
- }
-
-
- // MATCH FUNCTIONS
-
-
- /**
- * Locate the best instance of 'pattern' in 'text' near 'loc'.
- * Returns -1 if no match found.
- *
- * @param text The text to search.
- * @param pattern The pattern to search for.
- * @param loc The location to search around.
- * @return Best match index or -1.
- */
- public int matchMain(String text, String pattern, int loc) {
- // Check for null inputs.
- if (text == null || pattern == null) {
- throw new IllegalArgumentException("Null inputs. (matchMain)");
- }
-
- loc = Math.max(0, Math.min(loc, text.length()));
- if (text.equals(pattern)) {
- // Shortcut (potentially not guaranteed by the algorithm)
- return 0;
- } else if (text.length() == 0) {
- // Nothing to match.
- return -1;
- } else if (loc + pattern.length() <= text.length()
- && text.substring(loc, loc + pattern.length()).equals(pattern)) {
- // Perfect match at the perfect spot! (Includes case of null pattern)
- return loc;
- } else {
- // Do a fuzzy compare.
- return matchBitap(text, pattern, loc);
- }
- }
-
- /**
- * Locate the best instance of 'pattern' in 'text' near 'loc' using the
- * Bitap algorithm. Returns -1 if no match found.
- *
- * @param text The text to search.
- * @param pattern The pattern to search for.
- * @param loc The location to search around.
- * @return Best match index or -1.
- */
- protected int matchBitap(String text, String pattern, int loc) {
- assert (Match_MaxBits == 0 || pattern.length() <= Match_MaxBits)
- : "Pattern too long for this application.";
-
- // Initialise the alphabet.
- Map s = matchAlphabet(pattern);
-
- // Highest score beyond which we give up.
- double score_threshold = Match_Threshold;
- // Is there a nearby exact match? (speedup)
- int best_loc = text.indexOf(pattern, loc);
- if (best_loc != -1) {
- score_threshold = Math.min(matchBitapScore(0, best_loc, loc, pattern),
- score_threshold);
- // What about in the other direction? (speedup)
- best_loc = text.lastIndexOf(pattern, loc + pattern.length());
- if (best_loc != -1) {
- score_threshold = Math.min(matchBitapScore(0, best_loc, loc, pattern),
- score_threshold);
- }
- }
-
- // Initialise the bit arrays.
- int matchmask = 1 << (pattern.length() - 1);
- best_loc = -1;
-
- int bin_min, bin_mid;
- int bin_max = pattern.length() + text.length();
- // Empty initialization added to appease Java compiler.
- int[] last_rd = new int[0];
- for (int d = 0; d < pattern.length(); d++) {
- // Scan for the best match; each iteration allows for one more error.
- // Run a binary search to determine how far from 'loc' we can stray at
- // this error level.
- bin_min = 0;
- bin_mid = bin_max;
- while (bin_min < bin_mid) {
- if (matchBitapScore(d, loc + bin_mid, loc, pattern)
- <= score_threshold) {
- bin_min = bin_mid;
- } else {
- bin_max = bin_mid;
- }
- bin_mid = (bin_max - bin_min) / 2 + bin_min;
- }
- // Use the result from this iteration as the maximum for the next.
- bin_max = bin_mid;
- int start = Math.max(1, loc - bin_mid + 1);
- int finish = Math.min(loc + bin_mid, text.length()) + pattern.length();
-
- int[] rd = new int[finish + 2];
- rd[finish + 1] = (1 << d) - 1;
- for (int j = finish; j >= start; j--) {
- int charMatch;
- if (text.length() <= j - 1 || !s.containsKey(text.charAt(j - 1))) {
- // Out of range.
- charMatch = 0;
- } else {
- charMatch = s.get(text.charAt(j - 1));
- }
- if (d == 0) {
- // First pass: exact match.
- rd[j] = ((rd[j + 1] << 1) | 1) & charMatch;
- } else {
- // Subsequent passes: fuzzy match.
- rd[j] = (((rd[j + 1] << 1) | 1) & charMatch)
- | (((last_rd[j + 1] | last_rd[j]) << 1) | 1) | last_rd[j + 1];
- }
- if ((rd[j] & matchmask) != 0) {
- double score = matchBitapScore(d, j - 1, loc, pattern);
- // This match will almost certainly be better than any existing
- // match. But check anyway.
- if (score <= score_threshold) {
- // Told you so.
- score_threshold = score;
- best_loc = j - 1;
- if (best_loc > loc) {
- // When passing loc, don't exceed our current distance from loc.
- start = Math.max(1, 2 * loc - best_loc);
- } else {
- // Already passed loc, downhill from here on in.
- break;
- }
- }
- }
- }
- if (matchBitapScore(d + 1, loc, loc, pattern) > score_threshold) {
- // No hope for a (better) match at greater error levels.
- break;
- }
- last_rd = rd;
- }
- return best_loc;
- }
-
- /**
- * Compute and return the score for a match with e errors and x location.
- *
- * @param e Number of errors in match.
- * @param x Location of match.
- * @param loc Expected location of match.
- * @param pattern Pattern being sought.
- * @return Overall score for match (0.0 = good, 1.0 = bad).
- */
- private double matchBitapScore(int e, int x, int loc, String pattern) {
- float accuracy = (float) e / pattern.length();
- int proximity = Math.abs(loc - x);
- if (Match_Distance == 0) {
- // Dodge divide by zero error.
- return proximity == 0 ? accuracy : 1.0;
- }
- return accuracy + (proximity / (float) Match_Distance);
- }
-
- /**
- * Initialise the alphabet for the Bitap algorithm.
- *
- * @param pattern The text to encode.
- * @return Hash of character locations.
- */
- protected Map matchAlphabet(String pattern) {
- Map s = new HashMap();
- char[] char_pattern = pattern.toCharArray();
- for (char c : char_pattern) {
- s.put(c, 0);
- }
- int i = 0;
- for (char c : char_pattern) {
- s.put(c, s.get(c) | (1 << (pattern.length() - i - 1)));
- i++;
- }
- return s;
- }
-
-
- // PATCH FUNCTIONS
-
-
- /**
- * Increase the context until it is unique,
- * but don't let the pattern expand beyond Match_MaxBits.
- *
- * @param patch The patch to grow.
- * @param text Source text.
- */
- protected void patch_addContext(Patch patch, String text) {
- if (text.length() == 0) {
- return;
- }
- String pattern = text.substring(patch.start2, patch.start2 + patch.length1);
- int padding = 0;
-
- // Look for the first and last matches of pattern in text. If two different
- // matches are found, increase the pattern length.
- while (text.indexOf(pattern) != text.lastIndexOf(pattern)
- && pattern.length() < Match_MaxBits - Patch_Margin - Patch_Margin) {
- padding += Patch_Margin;
- pattern = text.substring(Math.max(0, patch.start2 - padding),
- Math.min(text.length(), patch.start2 + patch.length1 + padding));
- }
- // Add one chunk for good luck.
- padding += Patch_Margin;
-
- // Add the prefix.
- String prefix = text.substring(Math.max(0, patch.start2 - padding),
- patch.start2);
- if (prefix.length() != 0) {
- patch.diffs.addFirst(new Diff(Operation.EQUAL, prefix));
- }
- // Add the suffix.
- String suffix = text.substring(patch.start2 + patch.length1,
- Math.min(text.length(), patch.start2 + patch.length1 + padding));
- if (suffix.length() != 0) {
- patch.diffs.addLast(new Diff(Operation.EQUAL, suffix));
- }
-
- // Roll back the start points.
- patch.start1 -= prefix.length();
- patch.start2 -= prefix.length();
- // Extend the lengths.
- patch.length1 += prefix.length() + suffix.length();
- patch.length2 += prefix.length() + suffix.length();
- }
-
- /**
- * Compute a list of patches to turn text1 into text2.
- * A set of diffs will be computed.
- *
- * @param text1 Old text.
- * @param text2 New text.
- * @return LinkedList of Patch objects.
- */
- public LinkedList patchMake(String text1, String text2) {
- if (text1 == null || text2 == null) {
- throw new IllegalArgumentException("Null inputs. (patchMake)");
- }
- // No diffs provided, compute our own.
- LinkedList diffs = diffMain(text1, text2, true);
- if (diffs.size() > 2) {
- diffCleanupSemantic(diffs);
- diffCleanupEfficiency(diffs);
- }
- return patchMake(text1, diffs);
- }
-
- /**
- * Compute a list of patches to turn text1 into text2.
- * text1 will be derived from the provided diffs.
- *
- * @param diffs Array of Diff objects for text1 to text2.
- * @return LinkedList of Patch objects.
- */
- public LinkedList patchMake(LinkedList diffs) {
- if (diffs == null) {
- throw new IllegalArgumentException("Null inputs. (patchMake)");
- }
- // No origin string provided, compute our own.
- String text1 = diffText1(diffs);
- return patchMake(text1, diffs);
- }
-
- /**
- * Compute a list of patches to turn text1 into text2.
- * text2 is ignored, diffs are the delta between text1 and text2.
- *
- * @param text1 Old text
- * @param text2 Ignored.
- * @param diffs Array of Diff objects for text1 to text2.
- * @return LinkedList of Patch objects.
- * @deprecated Prefer patchMake(String text1, LinkedList diffs).
- */
- public LinkedList patchMake(String text1, String text2,
- LinkedList diffs) {
- return patchMake(text1, diffs);
- }
-
- /**
- * Compute a list of patches to turn text1 into text2.
- * text2 is not provided, diffs are the delta between text1 and text2.
- *
- * @param text1 Old text.
- * @param diffs Array of Diff objects for text1 to text2.
- * @return LinkedList of Patch objects.
- */
- public LinkedList patchMake(String text1, LinkedList diffs) {
- if (text1 == null || diffs == null) {
- throw new IllegalArgumentException("Null inputs. (patchMake)");
- }
-
- LinkedList patches = new LinkedList();
- if (diffs.isEmpty()) {
- return patches; // Get rid of the null case.
- }
- Patch patch = new Patch();
- int char_count1 = 0; // Number of characters into the text1 string.
- int char_count2 = 0; // Number of characters into the text2 string.
- // Start with text1 (prepatch_text) and apply the diffs until we arrive at
- // text2 (postpatch_text). We recreate the patches one by one to determine
- // context info.
- String prepatch_text = text1;
- String postpatch_text = text1;
- for (Diff aDiff : diffs) {
- if (patch.diffs.isEmpty() && aDiff.operation != Operation.EQUAL) {
- // A new patch starts here.
- patch.start1 = char_count1;
- patch.start2 = char_count2;
- }
-
- switch (aDiff.operation) {
- case INSERT:
- patch.diffs.add(aDiff);
- patch.length2 += aDiff.text.length();
- postpatch_text = postpatch_text.substring(0, char_count2)
- + aDiff.text + postpatch_text.substring(char_count2);
- break;
- case DELETE:
- patch.length1 += aDiff.text.length();
- patch.diffs.add(aDiff);
- postpatch_text = postpatch_text.substring(0, char_count2)
- + postpatch_text.substring(char_count2 + aDiff.text.length());
- break;
- case EQUAL:
- if (aDiff.text.length() <= 2 * Patch_Margin
- && !patch.diffs.isEmpty() && aDiff != diffs.getLast()) {
- // Small equality inside a patch.
- patch.diffs.add(aDiff);
- patch.length1 += aDiff.text.length();
- patch.length2 += aDiff.text.length();
- }
-
- if (aDiff.text.length() >= 2 * Patch_Margin) {
- // Time for a new patch.
- if (!patch.diffs.isEmpty()) {
- patch_addContext(patch, prepatch_text);
- patches.add(patch);
- patch = new Patch();
- // Unlike Unidiff, our patch lists have a rolling context.
- // http://code.google.com/p/google-diff-match-patch/wiki/Unidiff
- // Update prepatch text & pos to reflect the application of the
- // just completed patch.
- prepatch_text = postpatch_text;
- char_count1 = char_count2;
- }
- }
- break;
- }
-
- // Update the current character count.
- if (aDiff.operation != Operation.INSERT) {
- char_count1 += aDiff.text.length();
- }
- if (aDiff.operation != Operation.DELETE) {
- char_count2 += aDiff.text.length();
- }
- }
- // Pick up the leftover patch if not empty.
- if (!patch.diffs.isEmpty()) {
- patch_addContext(patch, prepatch_text);
- patches.add(patch);
- }
-
- return patches;
- }
-
- /**
- * Given an array of patches, return another array that is identical.
- *
- * @param patches Array of Patch objects.
- * @return Array of Patch objects.
- */
- public LinkedList patchDeepCopy(LinkedList patches) {
- LinkedList patchesCopy = new LinkedList();
- for (Patch aPatch : patches) {
- Patch patchCopy = new Patch();
- for (Diff aDiff : aPatch.diffs) {
- Diff diffCopy = new Diff(aDiff.operation, aDiff.text);
- patchCopy.diffs.add(diffCopy);
- }
- patchCopy.start1 = aPatch.start1;
- patchCopy.start2 = aPatch.start2;
- patchCopy.length1 = aPatch.length1;
- patchCopy.length2 = aPatch.length2;
- patchesCopy.add(patchCopy);
- }
- return patchesCopy;
- }
-
- /**
- * Merge a set of patches onto the text. Return a patched text, as well
- * as an array of true/false values indicating which patches were applied.
- *
- * @param patches Array of Patch objects
- * @param text Old text.
- * @return Two element Object array, containing the new text and an array of
- * boolean values.
- */
- public Object[] patchApply(LinkedList patches, String text) {
- if (patches.isEmpty()) {
- return new Object[]{text, new boolean[0]};
- }
-
- // Deep copy the patches so that no changes are made to originals.
- patches = patchDeepCopy(patches);
-
- String nullPadding = patchAddPadding(patches);
- text = nullPadding + text + nullPadding;
- patchSplitMax(patches);
-
- int x = 0;
- // delta keeps track of the offset between the expected and actual location
- // of the previous patch. If there are patches expected at positions 10 and
- // 20, but the first patch was found at 12, delta is 2 and the second patch
- // has an effective expected position of 22.
- int delta = 0;
- boolean[] results = new boolean[patches.size()];
- for (Patch aPatch : patches) {
- int expected_loc = aPatch.start2 + delta;
- String text1 = diffText1(aPatch.diffs);
- int start_loc;
- int end_loc = -1;
- if (text1.length() > this.Match_MaxBits) {
- // patchSplitMax will only provide an oversized pattern in the case of
- // a monster delete.
- start_loc = matchMain(text,
- text1.substring(0, this.Match_MaxBits), expected_loc);
- if (start_loc != -1) {
- end_loc = matchMain(text,
- text1.substring(text1.length() - this.Match_MaxBits),
- expected_loc + text1.length() - this.Match_MaxBits);
- if (end_loc == -1 || start_loc >= end_loc) {
- // Can't find valid trailing context. Drop this patch.
- start_loc = -1;
- }
- }
- } else {
- start_loc = matchMain(text, text1, expected_loc);
- }
- if (start_loc == -1) {
- // No match found. :(
- results[x] = false;
- // Subtract the delta for this failed patch from subsequent patches.
- delta -= aPatch.length2 - aPatch.length1;
- } else {
- // Found a match. :)
- results[x] = true;
- delta = start_loc - expected_loc;
- String text2;
- if (end_loc == -1) {
- text2 = text.substring(start_loc,
- Math.min(start_loc + text1.length(), text.length()));
- } else {
- text2 = text.substring(start_loc,
- Math.min(end_loc + this.Match_MaxBits, text.length()));
- }
- if (text1.equals(text2)) {
- // Perfect match, just shove the replacement text in.
- text = text.substring(0, start_loc) + diffText2(aPatch.diffs)
- + text.substring(start_loc + text1.length());
- } else {
- // Imperfect match. Run a diff to get a framework of equivalent
- // indices.
- LinkedList diffs = diffMain(text1, text2, false);
- if (text1.length() > this.Match_MaxBits
- && diffLevenshtein(diffs) / (float) text1.length()
- > this.Patch_DeleteThreshold) {
- // The end points match, but the content is unacceptably bad.
- results[x] = false;
- } else {
- diffCleanupSemanticLossless(diffs);
- int index1 = 0;
- for (Diff aDiff : aPatch.diffs) {
- if (aDiff.operation != Operation.EQUAL) {
- int index2 = diffXIndex(diffs, index1);
- if (aDiff.operation == Operation.INSERT) {
- // Insertion
- text = text.substring(0, start_loc + index2) + aDiff.text
- + text.substring(start_loc + index2);
- } else if (aDiff.operation == Operation.DELETE) {
- // Deletion
- text = text.substring(0, start_loc + index2)
- + text.substring(start_loc + diffXIndex(diffs,
- index1 + aDiff.text.length()));
- }
- }
- if (aDiff.operation != Operation.DELETE) {
- index1 += aDiff.text.length();
- }
- }
- }
- }
- }
- x++;
- }
- // Strip the padding off.
- text = text.substring(nullPadding.length(), text.length()
- - nullPadding.length());
- return new Object[]{text, results};
- }
-
- /**
- * Add some padding on text start and end so that edges can match something.
- * Intended to be called only from within patchApply.
- *
- * @param patches Array of Patch objects.
- * @return The padding string added to each side.
- */
- public String patchAddPadding(LinkedList patches) {
- short paddingLength = this.Patch_Margin;
- String nullPadding = "";
- for (short x = 1; x <= paddingLength; x++) {
- nullPadding += String.valueOf((char) x);
- }
-
- // Bump all the patches forward.
- for (Patch aPatch : patches) {
- aPatch.start1 += paddingLength;
- aPatch.start2 += paddingLength;
- }
-
- // Add some padding on start of first diff.
- Patch patch = patches.getFirst();
- LinkedList diffs = patch.diffs;
- if (diffs.isEmpty() || diffs.getFirst().operation != Operation.EQUAL) {
- // Add nullPadding equality.
- diffs.addFirst(new Diff(Operation.EQUAL, nullPadding));
- patch.start1 -= paddingLength; // Should be 0.
- patch.start2 -= paddingLength; // Should be 0.
- patch.length1 += paddingLength;
- patch.length2 += paddingLength;
- } else if (paddingLength > diffs.getFirst().text.length()) {
- // Grow first equality.
- Diff firstDiff = diffs.getFirst();
- int extraLength = paddingLength - firstDiff.text.length();
- firstDiff.text = nullPadding.substring(firstDiff.text.length())
- + firstDiff.text;
- patch.start1 -= extraLength;
- patch.start2 -= extraLength;
- patch.length1 += extraLength;
- patch.length2 += extraLength;
- }
-
- // Add some padding on end of last diff.
- patch = patches.getLast();
- diffs = patch.diffs;
- if (diffs.isEmpty() || diffs.getLast().operation != Operation.EQUAL) {
- // Add nullPadding equality.
- diffs.addLast(new Diff(Operation.EQUAL, nullPadding));
- patch.length1 += paddingLength;
- patch.length2 += paddingLength;
- } else if (paddingLength > diffs.getLast().text.length()) {
- // Grow last equality.
- Diff lastDiff = diffs.getLast();
- int extraLength = paddingLength - lastDiff.text.length();
- lastDiff.text += nullPadding.substring(0, extraLength);
- patch.length1 += extraLength;
- patch.length2 += extraLength;
- }
-
- return nullPadding;
- }
-
- /**
- * Look through the patches and break up any which are longer than the
- * maximum limit of the match algorithm.
- * Intended to be called only from within patchApply.
- *
- * @param patches LinkedList of Patch objects.
- */
- public void patchSplitMax(LinkedList patches) {
- short patch_size = Match_MaxBits;
- String precontext, postcontext;
- Patch patch;
- int start1, start2;
- boolean empty;
- Operation diff_type;
- String diff_text;
- ListIterator pointer = patches.listIterator();
- Patch bigpatch = pointer.hasNext() ? pointer.next() : null;
- while (bigpatch != null) {
- if (bigpatch.length1 <= Match_MaxBits) {
- bigpatch = pointer.hasNext() ? pointer.next() : null;
- continue;
- }
- // Remove the big old patch.
- pointer.remove();
- start1 = bigpatch.start1;
- start2 = bigpatch.start2;
- precontext = "";
- while (!bigpatch.diffs.isEmpty()) {
- // Create one of several smaller patches.
- patch = new Patch();
- empty = true;
- patch.start1 = start1 - precontext.length();
- patch.start2 = start2 - precontext.length();
- if (precontext.length() != 0) {
- patch.length1 = patch.length2 = precontext.length();
- patch.diffs.add(new Diff(Operation.EQUAL, precontext));
- }
- while (!bigpatch.diffs.isEmpty()
- && patch.length1 < patch_size - Patch_Margin) {
- diff_type = bigpatch.diffs.getFirst().operation;
- diff_text = bigpatch.diffs.getFirst().text;
- if (diff_type == Operation.INSERT) {
- // Insertions are harmless.
- patch.length2 += diff_text.length();
- start2 += diff_text.length();
- patch.diffs.addLast(bigpatch.diffs.removeFirst());
- empty = false;
- } else if (diff_type == Operation.DELETE && patch.diffs.size() == 1
- && patch.diffs.getFirst().operation == Operation.EQUAL
- && diff_text.length() > 2 * patch_size) {
- // This is a large deletion. Let it pass in one chunk.
- patch.length1 += diff_text.length();
- start1 += diff_text.length();
- empty = false;
- patch.diffs.add(new Diff(diff_type, diff_text));
- bigpatch.diffs.removeFirst();
- } else {
- // Deletion or equality. Only take as much as we can stomach.
- diff_text = diff_text.substring(0, Math.min(diff_text.length(),
- patch_size - patch.length1 - Patch_Margin));
- patch.length1 += diff_text.length();
- start1 += diff_text.length();
- if (diff_type == Operation.EQUAL) {
- patch.length2 += diff_text.length();
- start2 += diff_text.length();
- } else {
- empty = false;
- }
- patch.diffs.add(new Diff(diff_type, diff_text));
- if (diff_text.equals(bigpatch.diffs.getFirst().text)) {
- bigpatch.diffs.removeFirst();
- } else {
- bigpatch.diffs.getFirst().text = bigpatch.diffs.getFirst().text
- .substring(diff_text.length());
- }
- }
- }
- // Compute the head context for the next patch.
- precontext = diffText2(patch.diffs);
- precontext = precontext.substring(Math.max(0, precontext.length()
- - Patch_Margin));
- // Append the end context for this patch.
- if (diffText1(bigpatch.diffs).length() > Patch_Margin) {
- postcontext = diffText1(bigpatch.diffs).substring(0, Patch_Margin);
- } else {
- postcontext = diffText1(bigpatch.diffs);
- }
- if (postcontext.length() != 0) {
- patch.length1 += postcontext.length();
- patch.length2 += postcontext.length();
- if (!patch.diffs.isEmpty()
- && patch.diffs.getLast().operation == Operation.EQUAL) {
- patch.diffs.getLast().text += postcontext;
- } else {
- patch.diffs.add(new Diff(Operation.EQUAL, postcontext));
- }
- }
- if (!empty) {
- pointer.add(patch);
- }
- }
- bigpatch = pointer.hasNext() ? pointer.next() : null;
- }
- }
-
- /**
- * Take a list of patches and return a textual representation.
- *
- * @param patches List of Patch objects.
- * @return Text representation of patches.
- */
- public String patchToText(List patches) {
- StringBuilder text = new StringBuilder();
- for (Patch aPatch : patches) {
- text.append(aPatch);
- }
- return text.toString();
- }
-
- /**
- * Parse a textual representation of patches and return a List of Patch
- * objects.
- *
- * @param textline Text representation of patches.
- * @return List of Patch objects.
- * @throws IllegalArgumentException If invalid input.
- */
- public List patchFromText(String textline)
- throws IllegalArgumentException {
- List patches = new LinkedList();
- if (textline.length() == 0) {
- return patches;
- }
- List textList = Arrays.asList(textline.split("\n"));
- LinkedList text = new LinkedList(textList);
- Patch patch;
- Pattern patchHeader
- = Pattern.compile("^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$");
- Matcher m;
- char sign;
- String line;
- while (!text.isEmpty()) {
- m = patchHeader.matcher(text.getFirst());
- if (!m.matches()) {
- throw new IllegalArgumentException(
- "Invalid patch string: " + text.getFirst());
- }
- patch = new Patch();
- patches.add(patch);
- patch.start1 = Integer.parseInt(m.group(1));
- if (m.group(2).length() == 0) {
- patch.start1--;
- patch.length1 = 1;
- } else if (m.group(2).equals("0")) {
- patch.length1 = 0;
- } else {
- patch.start1--;
- patch.length1 = Integer.parseInt(m.group(2));
- }
-
- patch.start2 = Integer.parseInt(m.group(3));
- if (m.group(4).length() == 0) {
- patch.start2--;
- patch.length2 = 1;
- } else if (m.group(4).equals("0")) {
- patch.length2 = 0;
- } else {
- patch.start2--;
- patch.length2 = Integer.parseInt(m.group(4));
- }
- text.removeFirst();
-
- while (!text.isEmpty()) {
- try {
- sign = text.getFirst().charAt(0);
- } catch (IndexOutOfBoundsException e) {
- // Blank line? Whatever.
- text.removeFirst();
- continue;
- }
- line = text.getFirst().substring(1);
- line = line.replace("+", "%2B"); // decode would change all "+" to " "
- try {
- line = URLDecoder.decode(line, "UTF-8");
- } catch (UnsupportedEncodingException e) {
- // Not likely on modern system.
- throw new Error("This system does not support UTF-8.", e);
- } catch (IllegalArgumentException e) {
- // Malformed URI sequence.
- throw new IllegalArgumentException(
- "Illegal escape in patchFromText: " + line, e);
- }
- if (sign == '-') {
- // Deletion.
- patch.diffs.add(new Diff(Operation.DELETE, line));
- } else if (sign == '+') {
- // Insertion.
- patch.diffs.add(new Diff(Operation.INSERT, line));
- } else if (sign == ' ') {
- // Minor equality.
- patch.diffs.add(new Diff(Operation.EQUAL, line));
- } else if (sign == '@') {
- // Start of next patch.
- break;
- } else {
- // WTF?
- throw new IllegalArgumentException(
- "Invalid patch mode '" + sign + "' in: " + line);
- }
- text.removeFirst();
- }
- }
- return patches;
- }
-
-
-}
+/*
+ * Diff Match and Patch
+ * Copyright 2018 The diff-match-patch Authors.
+ * https://github.com/google/diff-match-patch
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ru.bsc.test.autotester.diff;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/*
+ * Functions for diff, match and patch.
+ * Computes the difference between two texts to create a patch.
+ * Applies the patch onto another text, allowing for errors.
+ *
+ * @author fraser@google.com (Neil Fraser)
+ */
+
+/**
+ * Class containing the diff, match and patch methods.
+ * Also contains the behaviour settings.
+ */
+@SuppressWarnings("all")
+public class DiffMatchPatch {
+
+ // Defaults.
+ // Set these on your diff_match_patch instance to override the defaults.
+
+ /**
+ * Number of seconds to map a diff before giving up (0 for infinity).
+ */
+ public float Diff_Timeout = 1.0f;
+ /**
+ * Cost of an empty edit operation in terms of edit characters.
+ */
+ public short Diff_EditCost = 4;
+ /**
+ * At what point is no match declared (0.0 = perfection, 1.0 = very loose).
+ */
+ public float Match_Threshold = 0.5f;
+ /**
+ * How far to search for a match (0 = exact location, 1000+ = broad match).
+ * A match this many characters away from the expected location will add
+ * 1.0 to the score (0.0 is a perfect match).
+ */
+ public int Match_Distance = 1000;
+ /**
+ * When deleting a large block of text (over ~64 characters), how close do
+ * the contents have to be to match the expected contents. (0.0 = perfection,
+ * 1.0 = very loose). Note that Match_Threshold controls how closely the
+ * end points of a delete need to match.
+ */
+ public float Patch_DeleteThreshold = 0.5f;
+ /**
+ * Chunk size for context length.
+ */
+ public short Patch_Margin = 4;
+
+ /**
+ * The number of bits in an int.
+ */
+ private short Match_MaxBits = 32;
+
+
+ // DIFF FUNCTIONS
+
+
+ /**
+ * Find the differences between two texts.
+ * Run a faster, slightly less optimal diff.
+ * This method allows the 'checklines' of diffMain() to be optional.
+ * Most of the time checklines is wanted, so default to true.
+ *
+ * @param text1 Old string to be diffed.
+ * @param text2 New string to be diffed.
+ * @return Linked List of Diff objects.
+ */
+ public LinkedList diffMain(String text1, String text2) {
+ return diffMain(text1, text2, true);
+ }
+
+ /**
+ * Find the differences between two texts.
+ *
+ * @param text1 Old string to be diffed.
+ * @param text2 New string to be diffed.
+ * @param checklines Speedup flag. If false, then don't run a
+ * line-level diff first to identify the changed areas.
+ * If true, then run a faster slightly less optimal diff.
+ * @return Linked List of Diff objects.
+ */
+ public LinkedList diffMain(String text1, String text2,
+ boolean checklines) {
+ // Set a deadline by which time the diff must be complete.
+ long deadline;
+ if (Diff_Timeout <= 0) {
+ deadline = Long.MAX_VALUE;
+ } else {
+ deadline = System.currentTimeMillis() + (long) (Diff_Timeout * 1000);
+ }
+ return diffMain(text1, text2, checklines, deadline);
+ }
+
+ /**
+ * Find the differences between two texts. Simplifies the problem by
+ * stripping any common prefix or suffix off the texts before diffing.
+ *
+ * @param text1 Old string to be diffed.
+ * @param text2 New string to be diffed.
+ * @param checklines Speedup flag. If false, then don't run a
+ * line-level diff first to identify the changed areas.
+ * If true, then run a faster slightly less optimal diff.
+ * @param deadline Time when the diff should be complete by. Used
+ * internally for recursive calls. Users should set DiffTimeout instead.
+ * @return Linked List of Diff objects.
+ */
+ private LinkedList diffMain(String text1, String text2,
+ boolean checklines, long deadline) {
+ // Check for null inputs.
+ if (text1 == null || text2 == null) {
+ throw new IllegalArgumentException("Null inputs. (diffMain)");
+ }
+
+ // Check for equality (speedup).
+ LinkedList diffs;
+ if (text1.equals(text2)) {
+ diffs = new LinkedList();
+ if (text1.length() != 0) {
+ diffs.add(new Diff(Operation.EQUAL, text1));
+ }
+ return diffs;
+ }
+
+ // Trim off common prefix (speedup).
+ int commonlength = diffCommonPrefix(text1, text2);
+ String commonprefix = text1.substring(0, commonlength);
+ text1 = text1.substring(commonlength);
+ text2 = text2.substring(commonlength);
+
+ // Trim off common suffix (speedup).
+ commonlength = diffCommonSuffix(text1, text2);
+ String commonsuffix = text1.substring(text1.length() - commonlength);
+ text1 = text1.substring(0, text1.length() - commonlength);
+ text2 = text2.substring(0, text2.length() - commonlength);
+
+ // Compute the diff on the middle block.
+ diffs = diffCompute(text1, text2, checklines, deadline);
+
+ // Restore the prefix and suffix.
+ if (commonprefix.length() != 0) {
+ diffs.addFirst(new Diff(Operation.EQUAL, commonprefix));
+ }
+ if (commonsuffix.length() != 0) {
+ diffs.addLast(new Diff(Operation.EQUAL, commonsuffix));
+ }
+
+ diffCleanupMerge(diffs);
+ return diffs;
+ }
+
+ /**
+ * Find the differences between two texts. Assumes that the texts do not
+ * have any common prefix or suffix.
+ *
+ * @param text1 Old string to be diffed.
+ * @param text2 New string to be diffed.
+ * @param checklines Speedup flag. If false, then don't run a
+ * line-level diff first to identify the changed areas.
+ * If true, then run a faster slightly less optimal diff.
+ * @param deadline Time when the diff should be complete by.
+ * @return Linked List of Diff objects.
+ */
+ private LinkedList diffCompute(String text1, String text2,
+ boolean checklines, long deadline) {
+ LinkedList diffs = new LinkedList();
+
+ if (text1.length() == 0) {
+ // Just add some text (speedup).
+ diffs.add(new Diff(Operation.INSERT, text2));
+ return diffs;
+ }
+
+ if (text2.length() == 0) {
+ // Just delete some text (speedup).
+ diffs.add(new Diff(Operation.DELETE, text1));
+ return diffs;
+ }
+
+ String longtext = text1.length() > text2.length() ? text1 : text2;
+ String shorttext = text1.length() > text2.length() ? text2 : text1;
+ int i = longtext.indexOf(shorttext);
+ if (i != -1) {
+ // Shorter text is inside the longer text (speedup).
+ Operation op = (text1.length() > text2.length()) ?
+ Operation.DELETE : Operation.INSERT;
+ diffs.add(new Diff(op, longtext.substring(0, i)));
+ diffs.add(new Diff(Operation.EQUAL, shorttext));
+ diffs.add(new Diff(op, longtext.substring(i + shorttext.length())));
+ return diffs;
+ }
+
+ if (shorttext.length() == 1) {
+ // Single character string.
+ // After the previous speedup, the character can't be an equality.
+ diffs.add(new Diff(Operation.DELETE, text1));
+ diffs.add(new Diff(Operation.INSERT, text2));
+ return diffs;
+ }
+
+ // Check to see if the problem can be split in two.
+ String[] hm = diffHalfMatch(text1, text2);
+ if (hm != null) {
+ // A half-match was found, sort out the return data.
+ String text1_a = hm[0];
+ String text1_b = hm[1];
+ String text2_a = hm[2];
+ String text2_b = hm[3];
+ String mid_common = hm[4];
+ // Send both pairs off for separate processing.
+ LinkedList diffs_a = diffMain(text1_a, text2_a,
+ checklines, deadline);
+ LinkedList diffs_b = diffMain(text1_b, text2_b,
+ checklines, deadline);
+ // Merge the results.
+ diffs = diffs_a;
+ diffs.add(new Diff(Operation.EQUAL, mid_common));
+ diffs.addAll(diffs_b);
+ return diffs;
+ }
+
+ if (checklines && text1.length() > 100 && text2.length() > 100) {
+ return diffLineMode(text1, text2, deadline);
+ }
+
+ return diffBisect(text1, text2, deadline);
+ }
+
+ /**
+ * Do a quick line-level diff on both strings, then rediff the parts for
+ * greater accuracy.
+ * This speedup can produce non-minimal diffs.
+ *
+ * @param text1 Old string to be diffed.
+ * @param text2 New string to be diffed.
+ * @param deadline Time when the diff should be complete by.
+ * @return Linked List of Diff objects.
+ */
+ private LinkedList diffLineMode(String text1, String text2,
+ long deadline) {
+ // Scan the text on a line-by-line basis first.
+ LinesToCharsResult b = diffLinesToChars(text1, text2);
+ text1 = b.chars1;
+ text2 = b.chars2;
+ List linearray = b.lineArray;
+
+ LinkedList diffs = diffMain(text1, text2, false, deadline);
+
+ // Convert the diff back to original text.
+ diffCharsToLines(diffs, linearray);
+ // Eliminate freak matches (e.g. blank lines)
+ diffCleanupSemantic(diffs);
+
+ // Rediff any replacement blocks, this time character-by-character.
+ // Add a dummy entry at the end.
+ diffs.add(new Diff(Operation.EQUAL, ""));
+ int count_delete = 0;
+ int count_insert = 0;
+ String text_delete = "";
+ String text_insert = "";
+ ListIterator pointer = diffs.listIterator();
+ Diff thisDiff = pointer.next();
+ while (thisDiff != null) {
+ switch (thisDiff.operation) {
+ case INSERT:
+ count_insert++;
+ text_insert += thisDiff.text;
+ break;
+ case DELETE:
+ count_delete++;
+ text_delete += thisDiff.text;
+ break;
+ case EQUAL:
+ // Upon reaching an equality, check for prior redundancies.
+ if (count_delete >= 1 && count_insert >= 1) {
+ // Delete the offending records and add the merged ones.
+ pointer.previous();
+ for (int j = 0; j < count_delete + count_insert; j++) {
+ pointer.previous();
+ pointer.remove();
+ }
+ for (Diff newDiff : diffMain(text_delete, text_insert, false,
+ deadline)) {
+ pointer.add(newDiff);
+ }
+ }
+ count_insert = 0;
+ count_delete = 0;
+ text_delete = "";
+ text_insert = "";
+ break;
+ }
+ thisDiff = pointer.hasNext() ? pointer.next() : null;
+ }
+ diffs.removeLast(); // Remove the dummy entry at the end.
+
+ return diffs;
+ }
+
+ /**
+ * Find the 'middle snake' of a diff, split the problem in two
+ * and return the recursively constructed diff.
+ * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
+ *
+ * @param text1 Old string to be diffed.
+ * @param text2 New string to be diffed.
+ * @param deadline Time at which to bail if not yet complete.
+ * @return LinkedList of Diff objects.
+ */
+ protected LinkedList diffBisect(String text1, String text2,
+ long deadline) {
+ // Cache the text lengths to prevent multiple calls.
+ int text1_length = text1.length();
+ int text2_length = text2.length();
+ int max_d = (text1_length + text2_length + 1) / 2;
+ int v_offset = max_d;
+ int v_length = 2 * max_d;
+ int[] v1 = new int[v_length];
+ int[] v2 = new int[v_length];
+ for (int x = 0; x < v_length; x++) {
+ v1[x] = -1;
+ v2[x] = -1;
+ }
+ v1[v_offset + 1] = 0;
+ v2[v_offset + 1] = 0;
+ int delta = text1_length - text2_length;
+ // If the total number of characters is odd, then the front path will
+ // collide with the reverse path.
+ boolean front = (delta % 2 != 0);
+ // Offsets for start and end of k loop.
+ // Prevents mapping of space beyond the grid.
+ int k1start = 0;
+ int k1end = 0;
+ int k2start = 0;
+ int k2end = 0;
+ for (int d = 0; d < max_d; d++) {
+ // Bail out if deadline is reached.
+ if (System.currentTimeMillis() > deadline) {
+ break;
+ }
+
+ // Walk the front path one step.
+ for (int k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
+ int k1_offset = v_offset + k1;
+ int x1;
+ if (k1 == -d || (k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1])) {
+ x1 = v1[k1_offset + 1];
+ } else {
+ x1 = v1[k1_offset - 1] + 1;
+ }
+ int y1 = x1 - k1;
+ while (x1 < text1_length && y1 < text2_length
+ && text1.charAt(x1) == text2.charAt(y1)) {
+ x1++;
+ y1++;
+ }
+ v1[k1_offset] = x1;
+ if (x1 > text1_length) {
+ // Ran off the right of the graph.
+ k1end += 2;
+ } else if (y1 > text2_length) {
+ // Ran off the bottom of the graph.
+ k1start += 2;
+ } else if (front) {
+ int k2_offset = v_offset + delta - k1;
+ if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) {
+ // Mirror x2 onto top-left coordinate system.
+ int x2 = text1_length - v2[k2_offset];
+ if (x1 >= x2) {
+ // Overlap detected.
+ return diffBisectSplit(text1, text2, x1, y1, deadline);
+ }
+ }
+ }
+ }
+
+ // Walk the reverse path one step.
+ for (int k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
+ int k2_offset = v_offset + k2;
+ int x2;
+ if (k2 == -d || (k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1])) {
+ x2 = v2[k2_offset + 1];
+ } else {
+ x2 = v2[k2_offset - 1] + 1;
+ }
+ int y2 = x2 - k2;
+ while (x2 < text1_length && y2 < text2_length
+ && text1.charAt(text1_length - x2 - 1)
+ == text2.charAt(text2_length - y2 - 1)) {
+ x2++;
+ y2++;
+ }
+ v2[k2_offset] = x2;
+ if (x2 > text1_length) {
+ // Ran off the left of the graph.
+ k2end += 2;
+ } else if (y2 > text2_length) {
+ // Ran off the top of the graph.
+ k2start += 2;
+ } else if (!front) {
+ int k1_offset = v_offset + delta - k2;
+ if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) {
+ int x1 = v1[k1_offset];
+ int y1 = v_offset + x1 - k1_offset;
+ // Mirror x2 onto top-left coordinate system.
+ x2 = text1_length - x2;
+ if (x1 >= x2) {
+ // Overlap detected.
+ return diffBisectSplit(text1, text2, x1, y1, deadline);
+ }
+ }
+ }
+ }
+ }
+ // Diff took too long and hit the deadline or
+ // number of diffs equals number of characters, no commonality at all.
+ LinkedList diffs = new LinkedList();
+ diffs.add(new Diff(Operation.DELETE, text1));
+ diffs.add(new Diff(Operation.INSERT, text2));
+ return diffs;
+ }
+
+ /**
+ * Given the location of the 'middle snake', split the diff in two parts
+ * and recurse.
+ *
+ * @param text1 Old string to be diffed.
+ * @param text2 New string to be diffed.
+ * @param x Index of split point in text1.
+ * @param y Index of split point in text2.
+ * @param deadline Time at which to bail if not yet complete.
+ * @return LinkedList of Diff objects.
+ */
+ private LinkedList diffBisectSplit(String text1, String text2,
+ int x, int y, long deadline) {
+ String text1a = text1.substring(0, x);
+ String text2a = text2.substring(0, y);
+ String text1b = text1.substring(x);
+ String text2b = text2.substring(y);
+
+ // Compute both diffs serially.
+ LinkedList diffs = diffMain(text1a, text2a, false, deadline);
+ LinkedList diffsb = diffMain(text1b, text2b, false, deadline);
+
+ diffs.addAll(diffsb);
+ return diffs;
+ }
+
+ /**
+ * Split two texts into a list of strings. Reduce the texts to a string of
+ * hashes where each Unicode character represents one line.
+ *
+ * @param text1 First string.
+ * @param text2 Second string.
+ * @return An object containing the encoded text1, the encoded text2 and
+ * the List of unique strings. The zeroth element of the List of
+ * unique strings is intentionally blank.
+ */
+ protected LinesToCharsResult diffLinesToChars(String text1, String text2) {
+ List lineArray = new ArrayList();
+ Map lineHash = new HashMap();
+ // e.g. linearray[4] == "Hello\n"
+ // e.g. linehash.get("Hello\n") == 4
+
+ // "\x00" is a valid character, but various debuggers don't like it.
+ // So we'll insert a junk entry to avoid generating a null character.
+ lineArray.add("");
+
+ String chars1 = diffLinesToCharsMunge(text1, lineArray, lineHash);
+ String chars2 = diffLinesToCharsMunge(text2, lineArray, lineHash);
+ return new LinesToCharsResult(chars1, chars2, lineArray);
+ }
+
+ /**
+ * Split a text into a list of strings. Reduce the texts to a string of
+ * hashes where each Unicode character represents one line.
+ *
+ * @param text String to encode.
+ * @param lineArray List of unique strings.
+ * @param lineHash Map of strings to indices.
+ * @return Encoded string.
+ */
+ private String diffLinesToCharsMunge(String text, List lineArray,
+ Map lineHash) {
+ int lineStart = 0;
+ int lineEnd = -1;
+ String line;
+ StringBuilder chars = new StringBuilder();
+ // Walk the text, pulling out a substring for each line.
+ // text.split('\n') would would temporarily double our memory footprint.
+ // Modifying text would create many large strings to garbage collect.
+ while (lineEnd < text.length() - 1) {
+ lineEnd = text.indexOf('\n', lineStart);
+ if (lineEnd == -1) {
+ lineEnd = text.length() - 1;
+ }
+ line = text.substring(lineStart, lineEnd + 1);
+ lineStart = lineEnd + 1;
+
+ if (lineHash.containsKey(line)) {
+ chars.append(String.valueOf((char) (int) lineHash.get(line)));
+ } else {
+ lineArray.add(line);
+ lineHash.put(line, lineArray.size() - 1);
+ chars.append(String.valueOf((char) (lineArray.size() - 1)));
+ }
+ }
+ return chars.toString();
+ }
+
+ /**
+ * Rehydrate the text in a diff from a string of line hashes to real lines of
+ * text.
+ *
+ * @param diffs LinkedList of Diff objects.
+ * @param lineArray List of unique strings.
+ */
+ protected void diffCharsToLines(LinkedList diffs,
+ List lineArray) {
+ StringBuilder text;
+ for (Diff diff : diffs) {
+ text = new StringBuilder();
+ for (int y = 0; y < diff.text.length(); y++) {
+ text.append(lineArray.get(diff.text.charAt(y)));
+ }
+ diff.text = text.toString();
+ }
+ }
+
+ /**
+ * Determine the common prefix of two strings
+ *
+ * @param text1 First string.
+ * @param text2 Second string.
+ * @return The number of characters common to the start of each string.
+ */
+ public int diffCommonPrefix(String text1, String text2) {
+ // Performance analysis: http://neil.fraser.name/news/2007/10/09/
+ int n = Math.min(text1.length(), text2.length());
+ for (int i = 0; i < n; i++) {
+ if (text1.charAt(i) != text2.charAt(i)) {
+ return i;
+ }
+ }
+ return n;
+ }
+
+ /**
+ * Determine the common suffix of two strings
+ *
+ * @param text1 First string.
+ * @param text2 Second string.
+ * @return The number of characters common to the end of each string.
+ */
+ public int diffCommonSuffix(String text1, String text2) {
+ // Performance analysis: http://neil.fraser.name/news/2007/10/09/
+ int text1_length = text1.length();
+ int text2_length = text2.length();
+ int n = Math.min(text1_length, text2_length);
+ for (int i = 1; i <= n; i++) {
+ if (text1.charAt(text1_length - i) != text2.charAt(text2_length - i)) {
+ return i - 1;
+ }
+ }
+ return n;
+ }
+
+ /**
+ * Determine if the suffix of one string is the prefix of another.
+ *
+ * @param text1 First string.
+ * @param text2 Second string.
+ * @return The number of characters common to the end of the first
+ * string and the start of the second string.
+ */
+ protected int diffCommonOverlap(String text1, String text2) {
+ // Cache the text lengths to prevent multiple calls.
+ int text1_length = text1.length();
+ int text2_length = text2.length();
+ // Eliminate the null case.
+ if (text1_length == 0 || text2_length == 0) {
+ return 0;
+ }
+ // Truncate the longer string.
+ if (text1_length > text2_length) {
+ text1 = text1.substring(text1_length - text2_length);
+ } else if (text1_length < text2_length) {
+ text2 = text2.substring(0, text1_length);
+ }
+ int text_length = Math.min(text1_length, text2_length);
+ // Quick check for the worst case.
+ if (text1.equals(text2)) {
+ return text_length;
+ }
+
+ // Start by looking for a single character match
+ // and increase length until no match is found.
+ // Performance analysis: http://neil.fraser.name/news/2010/11/04/
+ int best = 0;
+ int length = 1;
+ while (true) {
+ String pattern = text1.substring(text_length - length);
+ int found = text2.indexOf(pattern);
+ if (found == -1) {
+ return best;
+ }
+ length += found;
+ if (found == 0 || text1.substring(text_length - length).equals(
+ text2.substring(0, length))) {
+ best = length;
+ length++;
+ }
+ }
+ }
+
+ /**
+ * Do the two texts share a substring which is at least half the length of
+ * the longer text?
+ * This speedup can produce non-minimal diffs.
+ *
+ * @param text1 First string.
+ * @param text2 Second string.
+ * @return Five element String array, containing the prefix of text1, the
+ * suffix of text1, the prefix of text2, the suffix of text2 and the
+ * common middle. Or null if there was no match.
+ */
+ protected String[] diffHalfMatch(String text1, String text2) {
+ if (Diff_Timeout <= 0) {
+ // Don't risk returning a non-optimal diff if we have unlimited time.
+ return null;
+ }
+ String longtext = text1.length() > text2.length() ? text1 : text2;
+ String shorttext = text1.length() > text2.length() ? text2 : text1;
+ if (longtext.length() < 4 || shorttext.length() * 2 < longtext.length()) {
+ return null; // Pointless.
+ }
+
+ // First check if the second quarter is the seed for a half-match.
+ String[] hm1 = diffHalfMatchI(longtext, shorttext,
+ (longtext.length() + 3) / 4);
+ // Check again based on the third quarter.
+ String[] hm2 = diffHalfMatchI(longtext, shorttext,
+ (longtext.length() + 1) / 2);
+ String[] hm;
+ if (hm1 == null && hm2 == null) {
+ return null;
+ } else if (hm2 == null) {
+ hm = hm1;
+ } else if (hm1 == null) {
+ hm = hm2;
+ } else {
+ // Both matched. Select the longest.
+ hm = hm1[4].length() > hm2[4].length() ? hm1 : hm2;
+ }
+
+ // A half-match was found, sort out the return data.
+ if (text1.length() > text2.length()) {
+ return hm;
+ //return new String[]{hm[0], hm[1], hm[2], hm[3], hm[4]};
+ } else {
+ return new String[]{hm[2], hm[3], hm[0], hm[1], hm[4]};
+ }
+ }
+
+ /**
+ * Does a substring of shorttext exist within longtext such that the
+ * substring is at least half the length of longtext?
+ *
+ * @param longtext Longer string.
+ * @param shorttext Shorter string.
+ * @param i Start index of quarter length substring within longtext.
+ * @return Five element String array, containing the prefix of longtext, the
+ * suffix of longtext, the prefix of shorttext, the suffix of shorttext
+ * and the common middle. Or null if there was no match.
+ */
+ private String[] diffHalfMatchI(String longtext, String shorttext, int i) {
+ // Start with a 1/4 length substring at position i as a seed.
+ String seed = longtext.substring(i, i + longtext.length() / 4);
+ int j = -1;
+ String best_common = "";
+ String best_longtext_a = "", best_longtext_b = "";
+ String best_shorttext_a = "", best_shorttext_b = "";
+ while ((j = shorttext.indexOf(seed, j + 1)) != -1) {
+ int prefixLength = diffCommonPrefix(longtext.substring(i),
+ shorttext.substring(j));
+ int suffixLength = diffCommonSuffix(longtext.substring(0, i),
+ shorttext.substring(0, j));
+ if (best_common.length() < suffixLength + prefixLength) {
+ best_common = shorttext.substring(j - suffixLength, j)
+ + shorttext.substring(j, j + prefixLength);
+ best_longtext_a = longtext.substring(0, i - suffixLength);
+ best_longtext_b = longtext.substring(i + prefixLength);
+ best_shorttext_a = shorttext.substring(0, j - suffixLength);
+ best_shorttext_b = shorttext.substring(j + prefixLength);
+ }
+ }
+ if (best_common.length() * 2 >= longtext.length()) {
+ return new String[]{best_longtext_a, best_longtext_b,
+ best_shorttext_a, best_shorttext_b, best_common};
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Reduce the number of edits by eliminating semantically trivial equalities.
+ *
+ * @param diffs LinkedList of Diff objects.
+ */
+ public void diffCleanupSemantic(LinkedList diffs) {
+ if (diffs.isEmpty()) {
+ return;
+ }
+ boolean changes = false;
+ Stack equalities = new Stack(); // Stack of qualities.
+ String lastequality = null; // Always equal to equalities.lastElement().text
+ ListIterator pointer = diffs.listIterator();
+ // Number of characters that changed prior to the equality.
+ int length_insertions1 = 0;
+ int length_deletions1 = 0;
+ // Number of characters that changed after the equality.
+ int length_insertions2 = 0;
+ int length_deletions2 = 0;
+ Diff thisDiff = pointer.next();
+ while (thisDiff != null) {
+ if (thisDiff.operation == Operation.EQUAL) {
+ // Equality found.
+ equalities.push(thisDiff);
+ length_insertions1 = length_insertions2;
+ length_deletions1 = length_deletions2;
+ length_insertions2 = 0;
+ length_deletions2 = 0;
+ lastequality = thisDiff.text;
+ } else {
+ // An insertion or deletion.
+ if (thisDiff.operation == Operation.INSERT) {
+ length_insertions2 += thisDiff.text.length();
+ } else {
+ length_deletions2 += thisDiff.text.length();
+ }
+ // Eliminate an equality that is smaller or equal to the edits on both
+ // sides of it.
+ if (lastequality != null && (lastequality.length()
+ <= Math.max(length_insertions1, length_deletions1))
+ && (lastequality.length()
+ <= Math.max(length_insertions2, length_deletions2))) {
+ //System.out.println("Splitting: '" + lastequality + "'");
+ // Walk back to offending equality.
+ while (thisDiff != equalities.lastElement()) {
+ thisDiff = pointer.previous();
+ }
+ pointer.next();
+
+ // Replace equality with a delete.
+ pointer.set(new Diff(Operation.DELETE, lastequality));
+ // Insert a corresponding an insert.
+ pointer.add(new Diff(Operation.INSERT, lastequality));
+
+ equalities.pop(); // Throw away the equality we just deleted.
+ if (!equalities.empty()) {
+ // Throw away the previous equality (it needs to be reevaluated).
+ equalities.pop();
+ }
+ if (equalities.empty()) {
+ // There are no previous equalities, walk back to the start.
+ while (pointer.hasPrevious()) {
+ pointer.previous();
+ }
+ } else {
+ // There is a safe equality we can fall back to.
+ thisDiff = equalities.lastElement();
+ while (thisDiff != pointer.previous()) {
+ // Intentionally empty loop.
+ }
+ }
+
+ length_insertions1 = 0; // Reset the counters.
+ length_insertions2 = 0;
+ length_deletions1 = 0;
+ length_deletions2 = 0;
+ lastequality = null;
+ changes = true;
+ }
+ }
+ thisDiff = pointer.hasNext() ? pointer.next() : null;
+ }
+
+ // Normalize the diff.
+ if (changes) {
+ diffCleanupMerge(diffs);
+ }
+ diffCleanupSemanticLossless(diffs);
+
+ // Find any overlaps between deletions and insertions.
+ // e.g: abcxxxxxxdef
+ // -> abcxxxdef
+ // e.g: xxxabcdefxxx
+ // -> defxxxabc
+ // Only extract an overlap if it is as big as the edit ahead or behind it.
+ pointer = diffs.listIterator();
+ Diff prevDiff = null;
+ thisDiff = null;
+ if (pointer.hasNext()) {
+ prevDiff = pointer.next();
+ if (pointer.hasNext()) {
+ thisDiff = pointer.next();
+ }
+ }
+ while (thisDiff != null) {
+ if (prevDiff.operation == Operation.DELETE &&
+ thisDiff.operation == Operation.INSERT) {
+ String deletion = prevDiff.text;
+ String insertion = thisDiff.text;
+ int overlap_length1 = this.diffCommonOverlap(deletion, insertion);
+ int overlap_length2 = this.diffCommonOverlap(insertion, deletion);
+ if (overlap_length1 >= overlap_length2) {
+ if (overlap_length1 >= deletion.length() / 2.0 ||
+ overlap_length1 >= insertion.length() / 2.0) {
+ // Overlap found. Insert an equality and trim the surrounding edits.
+ pointer.previous();
+ pointer.add(new Diff(Operation.EQUAL,
+ insertion.substring(0, overlap_length1)));
+ prevDiff.text =
+ deletion.substring(0, deletion.length() - overlap_length1);
+ thisDiff.text = insertion.substring(overlap_length1);
+ // pointer.add inserts the element before the cursor, so there is
+ // no need to step past the new element.
+ }
+ } else {
+ if (overlap_length2 >= deletion.length() / 2.0 ||
+ overlap_length2 >= insertion.length() / 2.0) {
+ // Reverse overlap found.
+ // Insert an equality and swap and trim the surrounding edits.
+ pointer.previous();
+ pointer.add(new Diff(Operation.EQUAL,
+ deletion.substring(0, overlap_length2)));
+ prevDiff.operation = Operation.INSERT;
+ prevDiff.text =
+ insertion.substring(0, insertion.length() - overlap_length2);
+ thisDiff.operation = Operation.DELETE;
+ thisDiff.text = deletion.substring(overlap_length2);
+ // pointer.add inserts the element before the cursor, so there is
+ // no need to step past the new element.
+ }
+ }
+ thisDiff = pointer.hasNext() ? pointer.next() : null;
+ }
+ prevDiff = thisDiff;
+ thisDiff = pointer.hasNext() ? pointer.next() : null;
+ }
+ }
+
+ /**
+ * Look for single edits surrounded on both sides by equalities
+ * which can be shifted sideways to align the edit to a word boundary.
+ * e.g: The cat came. -> The cat came.
+ *
+ * @param diffs LinkedList of Diff objects.
+ */
+ public void diffCleanupSemanticLossless(LinkedList diffs) {
+ String equality1, edit, equality2;
+ String commonString;
+ int commonOffset;
+ int score, bestScore;
+ String bestEquality1, bestEdit, bestEquality2;
+ // Create a new iterator at the start.
+ ListIterator pointer = diffs.listIterator();
+ Diff prevDiff = pointer.hasNext() ? pointer.next() : null;
+ Diff thisDiff = pointer.hasNext() ? pointer.next() : null;
+ Diff nextDiff = pointer.hasNext() ? pointer.next() : null;
+ // Intentionally ignore the first and last element (don't need checking).
+ while (nextDiff != null) {
+ if (prevDiff.operation == Operation.EQUAL &&
+ nextDiff.operation == Operation.EQUAL) {
+ // This is a single edit surrounded by equalities.
+ equality1 = prevDiff.text;
+ edit = thisDiff.text;
+ equality2 = nextDiff.text;
+
+ // First, shift the edit as far left as possible.
+ commonOffset = diffCommonSuffix(equality1, edit);
+ if (commonOffset != 0) {
+ commonString = edit.substring(edit.length() - commonOffset);
+ equality1 = equality1.substring(0, equality1.length() - commonOffset);
+ edit = commonString + edit.substring(0, edit.length() - commonOffset);
+ equality2 = commonString + equality2;
+ }
+
+ // Second, step character by character right, looking for the best fit.
+ bestEquality1 = equality1;
+ bestEdit = edit;
+ bestEquality2 = equality2;
+ bestScore = diffCleanupSemanticScore(equality1, edit)
+ + diffCleanupSemanticScore(edit, equality2);
+ while (edit.length() != 0 && equality2.length() != 0
+ && edit.charAt(0) == equality2.charAt(0)) {
+ equality1 += edit.charAt(0);
+ edit = edit.substring(1) + equality2.charAt(0);
+ equality2 = equality2.substring(1);
+ score = diffCleanupSemanticScore(equality1, edit)
+ + diffCleanupSemanticScore(edit, equality2);
+ // The >= encourages trailing rather than leading whitespace on edits.
+ if (score >= bestScore) {
+ bestScore = score;
+ bestEquality1 = equality1;
+ bestEdit = edit;
+ bestEquality2 = equality2;
+ }
+ }
+
+ if (!prevDiff.text.equals(bestEquality1)) {
+ // We have an improvement, save it back to the diff.
+ if (bestEquality1.length() != 0) {
+ prevDiff.text = bestEquality1;
+ } else {
+ pointer.previous(); // Walk past nextDiff.
+ pointer.previous(); // Walk past thisDiff.
+ pointer.previous(); // Walk past prevDiff.
+ pointer.remove(); // Delete prevDiff.
+ pointer.next(); // Walk past thisDiff.
+ pointer.next(); // Walk past nextDiff.
+ }
+ thisDiff.text = bestEdit;
+ if (bestEquality2.length() != 0) {
+ nextDiff.text = bestEquality2;
+ } else {
+ pointer.remove(); // Delete nextDiff.
+ nextDiff = thisDiff;
+ thisDiff = prevDiff;
+ }
+ }
+ }
+ prevDiff = thisDiff;
+ thisDiff = nextDiff;
+ nextDiff = pointer.hasNext() ? pointer.next() : null;
+ }
+ }
+
+ /**
+ * Given two strings, compute a score representing whether the internal
+ * boundary falls on logical boundaries.
+ * Scores range from 6 (best) to 0 (worst).
+ *
+ * @param one First string.
+ * @param two Second string.
+ * @return The score.
+ */
+ private int diffCleanupSemanticScore(String one, String two) {
+ if (one.length() == 0 || two.length() == 0) {
+ // Edges are the best.
+ return 6;
+ }
+
+ // Each port of this function behaves slightly differently due to
+ // subtle differences in each language's definition of things like
+ // 'whitespace'. Since this function's purpose is largely cosmetic,
+ // the choice has been made to use each language's native features
+ // rather than force total conformity.
+ char char1 = one.charAt(one.length() - 1);
+ char char2 = two.charAt(0);
+ boolean nonAlphaNumeric1 = !Character.isLetterOrDigit(char1);
+ boolean nonAlphaNumeric2 = !Character.isLetterOrDigit(char2);
+ boolean whitespace1 = nonAlphaNumeric1 && Character.isWhitespace(char1);
+ boolean whitespace2 = nonAlphaNumeric2 && Character.isWhitespace(char2);
+ boolean lineBreak1 = whitespace1
+ && Character.getType(char1) == Character.CONTROL;
+ boolean lineBreak2 = whitespace2
+ && Character.getType(char2) == Character.CONTROL;
+ boolean blankLine1 = lineBreak1 && BLANKLINEEND.matcher(one).find();
+ boolean blankLine2 = lineBreak2 && BLANKLINESTART.matcher(two).find();
+
+ if (blankLine1 || blankLine2) {
+ // Five points for blank lines.
+ return 5;
+ } else if (lineBreak1 || lineBreak2) {
+ // Four points for line breaks.
+ return 4;
+ } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) {
+ // Three points for end of sentences.
+ return 3;
+ } else if (whitespace1 || whitespace2) {
+ // Two points for whitespace.
+ return 2;
+ } else if (nonAlphaNumeric1 || nonAlphaNumeric2) {
+ // One point for non-alphanumeric.
+ return 1;
+ }
+ return 0;
+ }
+
+ // Define some regex patterns for matching boundaries.
+ private Pattern BLANKLINEEND
+ = Pattern.compile("\\n\\r?\\n\\Z", Pattern.DOTALL);
+ private Pattern BLANKLINESTART
+ = Pattern.compile("\\A\\r?\\n\\r?\\n", Pattern.DOTALL);
+
+ /**
+ * Reduce the number of edits by eliminating operationally trivial equalities.
+ *
+ * @param diffs LinkedList of Diff objects.
+ */
+ public void diffCleanupEfficiency(LinkedList diffs) {
+ if (diffs.isEmpty()) {
+ return;
+ }
+ boolean changes = false;
+ Stack equalities = new Stack(); // Stack of equalities.
+ String lastequality = null; // Always equal to equalities.lastElement().text
+ ListIterator pointer = diffs.listIterator();
+ // Is there an insertion operation before the last equality.
+ boolean pre_ins = false;
+ // Is there a deletion operation before the last equality.
+ boolean pre_del = false;
+ // Is there an insertion operation after the last equality.
+ boolean post_ins = false;
+ // Is there a deletion operation after the last equality.
+ boolean post_del = false;
+ Diff thisDiff = pointer.next();
+ Diff safeDiff = thisDiff; // The last Diff that is known to be unsplitable.
+ while (thisDiff != null) {
+ if (thisDiff.operation == Operation.EQUAL) {
+ // Equality found.
+ if (thisDiff.text.length() < Diff_EditCost && (post_ins || post_del)) {
+ // Candidate found.
+ equalities.push(thisDiff);
+ pre_ins = post_ins;
+ pre_del = post_del;
+ lastequality = thisDiff.text;
+ } else {
+ // Not a candidate, and can never become one.
+ equalities.clear();
+ lastequality = null;
+ safeDiff = thisDiff;
+ }
+ post_ins = post_del = false;
+ } else {
+ // An insertion or deletion.
+ if (thisDiff.operation == Operation.DELETE) {
+ post_del = true;
+ } else {
+ post_ins = true;
+ }
+ /*
+ * Five types to be split:
+ * ABXYCD
+ * AXCD
+ * ABXC
+ * AXCD
+ * ABXC
+ */
+ if (lastequality != null
+ && ((pre_ins && pre_del && post_ins && post_del)
+ || ((lastequality.length() < Diff_EditCost / 2)
+ && ((pre_ins ? 1 : 0) + (pre_del ? 1 : 0)
+ + (post_ins ? 1 : 0) + (post_del ? 1 : 0)) == 3))) {
+ //System.out.println("Splitting: '" + lastequality + "'");
+ // Walk back to offending equality.
+ while (thisDiff != equalities.lastElement()) {
+ thisDiff = pointer.previous();
+ }
+ pointer.next();
+
+ // Replace equality with a delete.
+ pointer.set(new Diff(Operation.DELETE, lastequality));
+ // Insert a corresponding an insert.
+ pointer.add(thisDiff = new Diff(Operation.INSERT, lastequality));
+
+ equalities.pop(); // Throw away the equality we just deleted.
+ lastequality = null;
+ if (pre_ins && pre_del) {
+ // No changes made which could affect previous entry, keep going.
+ post_ins = post_del = true;
+ equalities.clear();
+ safeDiff = thisDiff;
+ } else {
+ if (!equalities.empty()) {
+ // Throw away the previous equality (it needs to be reevaluated).
+ equalities.pop();
+ }
+ if (equalities.empty()) {
+ // There are no previous questionable equalities,
+ // walk back to the last known safe diff.
+ thisDiff = safeDiff;
+ } else {
+ // There is an equality we can fall back to.
+ thisDiff = equalities.lastElement();
+ }
+ while (thisDiff != pointer.previous()) {
+ // Intentionally empty loop.
+ }
+ post_ins = post_del = false;
+ }
+
+ changes = true;
+ }
+ }
+ thisDiff = pointer.hasNext() ? pointer.next() : null;
+ }
+
+ if (changes) {
+ diffCleanupMerge(diffs);
+ }
+ }
+
+ /**
+ * Reorder and merge like edit sections. Merge equalities.
+ * Any edit section can move as long as it doesn't cross an equality.
+ *
+ * @param diffs LinkedList of Diff objects.
+ */
+ public void diffCleanupMerge(LinkedList diffs) {
+ diffs.add(new Diff(Operation.EQUAL, "")); // Add a dummy entry at the end.
+ ListIterator pointer = diffs.listIterator();
+ int count_delete = 0;
+ int count_insert = 0;
+ String text_delete = "";
+ String text_insert = "";
+ Diff thisDiff = pointer.next();
+ Diff prevEqual = null;
+ int commonlength;
+ while (thisDiff != null) {
+ switch (thisDiff.operation) {
+ case INSERT:
+ count_insert++;
+ text_insert += thisDiff.text;
+ prevEqual = null;
+ break;
+ case DELETE:
+ count_delete++;
+ text_delete += thisDiff.text;
+ prevEqual = null;
+ break;
+ case EQUAL:
+ if (count_delete + count_insert > 1) {
+ boolean both_types = count_delete != 0 && count_insert != 0;
+ // Delete the offending records.
+ pointer.previous(); // Reverse direction.
+ while (count_delete-- > 0) {
+ pointer.previous();
+ pointer.remove();
+ }
+ while (count_insert-- > 0) {
+ pointer.previous();
+ pointer.remove();
+ }
+ if (both_types) {
+ // Factor out any common prefixies.
+ commonlength = diffCommonPrefix(text_insert, text_delete);
+ if (commonlength != 0) {
+ if (pointer.hasPrevious()) {
+ thisDiff = pointer.previous();
+ assert thisDiff.operation == Operation.EQUAL
+ : "Previous diff should have been an equality.";
+ thisDiff.text += text_insert.substring(0, commonlength);
+ pointer.next();
+ } else {
+ pointer.add(new Diff(Operation.EQUAL,
+ text_insert.substring(0, commonlength)));
+ }
+ text_insert = text_insert.substring(commonlength);
+ text_delete = text_delete.substring(commonlength);
+ }
+ // Factor out any common suffixies.
+ commonlength = diffCommonSuffix(text_insert, text_delete);
+ if (commonlength != 0) {
+ thisDiff = pointer.next();
+ thisDiff.text = text_insert.substring(text_insert.length()
+ - commonlength) + thisDiff.text;
+ text_insert = text_insert.substring(0, text_insert.length()
+ - commonlength);
+ text_delete = text_delete.substring(0, text_delete.length()
+ - commonlength);
+ pointer.previous();
+ }
+ }
+ // Insert the merged records.
+ if (text_delete.length() != 0) {
+ pointer.add(new Diff(Operation.DELETE, text_delete));
+ }
+ if (text_insert.length() != 0) {
+ pointer.add(new Diff(Operation.INSERT, text_insert));
+ }
+ // Step forward to the equality.
+ thisDiff = pointer.hasNext() ? pointer.next() : null;
+ } else if (prevEqual != null) {
+ // Merge this equality with the previous one.
+ prevEqual.text += thisDiff.text;
+ pointer.remove();
+ thisDiff = pointer.previous();
+ pointer.next(); // Forward direction
+ }
+ count_insert = 0;
+ count_delete = 0;
+ text_delete = "";
+ text_insert = "";
+ prevEqual = thisDiff;
+ break;
+ }
+ thisDiff = pointer.hasNext() ? pointer.next() : null;
+ }
+ if (diffs.getLast().text.length() == 0) {
+ diffs.removeLast(); // Remove the dummy entry at the end.
+ }
+
+ /*
+ * Second pass: look for single edits surrounded on both sides by equalities
+ * which can be shifted sideways to eliminate an equality.
+ * e.g: ABAC -> ABAC
+ */
+ boolean changes = false;
+ // Create a new iterator at the start.
+ // (As opposed to walking the current one back.)
+ pointer = diffs.listIterator();
+ Diff prevDiff = pointer.hasNext() ? pointer.next() : null;
+ thisDiff = pointer.hasNext() ? pointer.next() : null;
+ Diff nextDiff = pointer.hasNext() ? pointer.next() : null;
+ // Intentionally ignore the first and last element (don't need checking).
+ while (nextDiff != null) {
+ if (prevDiff.operation == Operation.EQUAL &&
+ nextDiff.operation == Operation.EQUAL) {
+ // This is a single edit surrounded by equalities.
+ if (thisDiff.text.endsWith(prevDiff.text)) {
+ // Shift the edit over the previous equality.
+ thisDiff.text = prevDiff.text
+ + thisDiff.text.substring(0, thisDiff.text.length()
+ - prevDiff.text.length());
+ nextDiff.text = prevDiff.text + nextDiff.text;
+ pointer.previous(); // Walk past nextDiff.
+ pointer.previous(); // Walk past thisDiff.
+ pointer.previous(); // Walk past prevDiff.
+ pointer.remove(); // Delete prevDiff.
+ pointer.next(); // Walk past thisDiff.
+ thisDiff = pointer.next(); // Walk past nextDiff.
+ nextDiff = pointer.hasNext() ? pointer.next() : null;
+ changes = true;
+ } else if (thisDiff.text.startsWith(nextDiff.text)) {
+ // Shift the edit over the next equality.
+ prevDiff.text += nextDiff.text;
+ thisDiff.text = thisDiff.text.substring(nextDiff.text.length())
+ + nextDiff.text;
+ pointer.remove(); // Delete nextDiff.
+ nextDiff = pointer.hasNext() ? pointer.next() : null;
+ changes = true;
+ }
+ }
+ prevDiff = thisDiff;
+ thisDiff = nextDiff;
+ nextDiff = pointer.hasNext() ? pointer.next() : null;
+ }
+ // If shifts were made, the diff needs reordering and another shift sweep.
+ if (changes) {
+ diffCleanupMerge(diffs);
+ }
+ }
+
+ /**
+ * loc is a location in text1, compute and return the equivalent location in
+ * text2.
+ * e.g. "The cat" vs "The big cat", 1->1, 5->8
+ *
+ * @param diffs LinkedList of Diff objects.
+ * @param loc Location within text1.
+ * @return Location within text2.
+ */
+ public int diffXIndex(LinkedList diffs, int loc) {
+ int chars1 = 0;
+ int chars2 = 0;
+ int last_chars1 = 0;
+ int last_chars2 = 0;
+ Diff lastDiff = null;
+ for (Diff aDiff : diffs) {
+ if (aDiff.operation != Operation.INSERT) {
+ // Equality or deletion.
+ chars1 += aDiff.text.length();
+ }
+ if (aDiff.operation != Operation.DELETE) {
+ // Equality or insertion.
+ chars2 += aDiff.text.length();
+ }
+ if (chars1 > loc) {
+ // Overshot the location.
+ lastDiff = aDiff;
+ break;
+ }
+ last_chars1 = chars1;
+ last_chars2 = chars2;
+ }
+ if (lastDiff != null && lastDiff.operation == Operation.DELETE) {
+ // The location was deleted.
+ return last_chars2;
+ }
+ // Add the remaining character length.
+ return last_chars2 + (loc - last_chars1);
+ }
+
+ /**
+ * Convert a Diff list into a pretty HTML report.
+ *
+ * @param diffs LinkedList of Diff objects.
+ * @return HTML representation.
+ */
+ public String diffPrettyHtml(LinkedList diffs) {
+ StringBuilder html = new StringBuilder();
+ for (Diff aDiff : diffs) {
+ String text = aDiff.text.replace("&", "&").replace("<", "<")
+ .replace(">", ">").replace("\n", "¶
");
+ switch (aDiff.operation) {
+ case INSERT:
+ html.append("").append(text)
+ .append("");
+ break;
+ case DELETE:
+ html.append("").append(text)
+ .append("");
+ break;
+ case EQUAL:
+ html.append("").append(text).append("");
+ break;
+ }
+ }
+ return html.toString();
+ }
+
+ /**
+ * Compute and return the source text (all equalities and deletions).
+ *
+ * @param diffs LinkedList of Diff objects.
+ * @return Source text.
+ */
+ public String diffText1(LinkedList diffs) {
+ StringBuilder text = new StringBuilder();
+ for (Diff aDiff : diffs) {
+ if (aDiff.operation != Operation.INSERT) {
+ text.append(aDiff.text);
+ }
+ }
+ return text.toString();
+ }
+
+ /**
+ * Compute and return the destination text (all equalities and insertions).
+ *
+ * @param diffs LinkedList of Diff objects.
+ * @return Destination text.
+ */
+ public String diffText2(LinkedList diffs) {
+ StringBuilder text = new StringBuilder();
+ for (Diff aDiff : diffs) {
+ if (aDiff.operation != Operation.DELETE) {
+ text.append(aDiff.text);
+ }
+ }
+ return text.toString();
+ }
+
+ /**
+ * Compute the Levenshtein distance; the number of inserted, deleted or
+ * substituted characters.
+ *
+ * @param diffs LinkedList of Diff objects.
+ * @return Number of changes.
+ */
+ public int diffLevenshtein(LinkedList diffs) {
+ int levenshtein = 0;
+ int insertions = 0;
+ int deletions = 0;
+ for (Diff aDiff : diffs) {
+ switch (aDiff.operation) {
+ case INSERT:
+ insertions += aDiff.text.length();
+ break;
+ case DELETE:
+ deletions += aDiff.text.length();
+ break;
+ case EQUAL:
+ // A deletion and an insertion is one substitution.
+ levenshtein += Math.max(insertions, deletions);
+ insertions = 0;
+ deletions = 0;
+ break;
+ }
+ }
+ levenshtein += Math.max(insertions, deletions);
+ return levenshtein;
+ }
+
+ /**
+ * Crush the diff into an encoded string which describes the operations
+ * required to transform text1 into text2.
+ * E.g. =3\t-2\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'.
+ * Operations are tab-separated. Inserted text is escaped using %xx notation.
+ *
+ * @param diffs Array of Diff objects.
+ * @return Delta text.
+ */
+ public String diffToDelta(LinkedList diffs) {
+ StringBuilder text = new StringBuilder();
+ for (Diff aDiff : diffs) {
+ switch (aDiff.operation) {
+ case INSERT:
+ try {
+ text.append("+").append(URLEncoder.encode(aDiff.text, "UTF-8")
+ .replace('+', ' ')).append("\t");
+ } catch (UnsupportedEncodingException e) {
+ // Not likely on modern system.
+ throw new Error("This system does not support UTF-8.", e);
+ }
+ break;
+ case DELETE:
+ text.append("-").append(aDiff.text.length()).append("\t");
+ break;
+ case EQUAL:
+ text.append("=").append(aDiff.text.length()).append("\t");
+ break;
+ }
+ }
+ String delta = text.toString();
+ if (delta.length() != 0) {
+ // Strip off trailing tab character.
+ delta = delta.substring(0, delta.length() - 1);
+ delta = new Patch().unescapeForEncodeUriCompatability(delta);
+ }
+ return delta;
+ }
+
+ /**
+ * Given the original text1, and an encoded string which describes the
+ * operations required to transform text1 into text2, compute the full diff.
+ *
+ * @param text1 Source string for the diff.
+ * @param delta Delta text.
+ * @return Array of Diff objects or null if invalid.
+ * @throws IllegalArgumentException If invalid input.
+ */
+ public LinkedList diffFromDelta(String text1, String delta)
+ throws IllegalArgumentException {
+ LinkedList diffs = new LinkedList();
+ int pointer = 0; // Cursor in text1
+ String[] tokens = delta.split("\t");
+ for (String token : tokens) {
+ if (token.length() == 0) {
+ // Blank tokens are ok (from a trailing \t).
+ continue;
+ }
+ // Each token begins with a one character parameter which specifies the
+ // operation of this token (delete, insert, equality).
+ String param = token.substring(1);
+ switch (token.charAt(0)) {
+ case '+':
+ // decode would change all "+" to " "
+ param = param.replace("+", "%2B");
+ try {
+ param = URLDecoder.decode(param, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // Not likely on modern system.
+ throw new Error("This system does not support UTF-8.", e);
+ } catch (IllegalArgumentException e) {
+ // Malformed URI sequence.
+ throw new IllegalArgumentException(
+ "Illegal escape in diffFromDelta: " + param, e);
+ }
+ diffs.add(new Diff(Operation.INSERT, param));
+ break;
+ case '-':
+ // Fall through.
+ case '=':
+ int n;
+ try {
+ n = Integer.parseInt(param);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(
+ "Invalid number in diffFromDelta: " + param, e);
+ }
+ if (n < 0) {
+ throw new IllegalArgumentException(
+ "Negative number in diffFromDelta: " + param);
+ }
+ String text;
+ try {
+ text = text1.substring(pointer, pointer += n);
+ } catch (StringIndexOutOfBoundsException e) {
+ throw new IllegalArgumentException("Delta length (" + pointer
+ + ") larger than source text length (" + text1.length()
+ + ").", e);
+ }
+ if (token.charAt(0) == '=') {
+ diffs.add(new Diff(Operation.EQUAL, text));
+ } else {
+ diffs.add(new Diff(Operation.DELETE, text));
+ }
+ break;
+ default:
+ // Anything else is an error.
+ throw new IllegalArgumentException(
+ "Invalid diff operation in diffFromDelta: " + token.charAt(0));
+ }
+ }
+ if (pointer != text1.length()) {
+ throw new IllegalArgumentException("Delta length (" + pointer
+ + ") smaller than source text length (" + text1.length() + ").");
+ }
+ return diffs;
+ }
+
+
+ // MATCH FUNCTIONS
+
+
+ /**
+ * Locate the best instance of 'pattern' in 'text' near 'loc'.
+ * Returns -1 if no match found.
+ *
+ * @param text The text to search.
+ * @param pattern The pattern to search for.
+ * @param loc The location to search around.
+ * @return Best match index or -1.
+ */
+ public int matchMain(String text, String pattern, int loc) {
+ // Check for null inputs.
+ if (text == null || pattern == null) {
+ throw new IllegalArgumentException("Null inputs. (matchMain)");
+ }
+
+ loc = Math.max(0, Math.min(loc, text.length()));
+ if (text.equals(pattern)) {
+ // Shortcut (potentially not guaranteed by the algorithm)
+ return 0;
+ } else if (text.length() == 0) {
+ // Nothing to match.
+ return -1;
+ } else if (loc + pattern.length() <= text.length()
+ && text.substring(loc, loc + pattern.length()).equals(pattern)) {
+ // Perfect match at the perfect spot! (Includes case of null pattern)
+ return loc;
+ } else {
+ // Do a fuzzy compare.
+ return matchBitap(text, pattern, loc);
+ }
+ }
+
+ /**
+ * Locate the best instance of 'pattern' in 'text' near 'loc' using the
+ * Bitap algorithm. Returns -1 if no match found.
+ *
+ * @param text The text to search.
+ * @param pattern The pattern to search for.
+ * @param loc The location to search around.
+ * @return Best match index or -1.
+ */
+ protected int matchBitap(String text, String pattern, int loc) {
+ assert (Match_MaxBits == 0 || pattern.length() <= Match_MaxBits)
+ : "Pattern too long for this application.";
+
+ // Initialise the alphabet.
+ Map s = matchAlphabet(pattern);
+
+ // Highest score beyond which we give up.
+ double score_threshold = Match_Threshold;
+ // Is there a nearby exact match? (speedup)
+ int best_loc = text.indexOf(pattern, loc);
+ if (best_loc != -1) {
+ score_threshold = Math.min(matchBitapScore(0, best_loc, loc, pattern),
+ score_threshold);
+ // What about in the other direction? (speedup)
+ best_loc = text.lastIndexOf(pattern, loc + pattern.length());
+ if (best_loc != -1) {
+ score_threshold = Math.min(matchBitapScore(0, best_loc, loc, pattern),
+ score_threshold);
+ }
+ }
+
+ // Initialise the bit arrays.
+ int matchmask = 1 << (pattern.length() - 1);
+ best_loc = -1;
+
+ int bin_min, bin_mid;
+ int bin_max = pattern.length() + text.length();
+ // Empty initialization added to appease Java compiler.
+ int[] last_rd = new int[0];
+ for (int d = 0; d < pattern.length(); d++) {
+ // Scan for the best match; each iteration allows for one more error.
+ // Run a binary search to determine how far from 'loc' we can stray at
+ // this error level.
+ bin_min = 0;
+ bin_mid = bin_max;
+ while (bin_min < bin_mid) {
+ if (matchBitapScore(d, loc + bin_mid, loc, pattern)
+ <= score_threshold) {
+ bin_min = bin_mid;
+ } else {
+ bin_max = bin_mid;
+ }
+ bin_mid = (bin_max - bin_min) / 2 + bin_min;
+ }
+ // Use the result from this iteration as the maximum for the next.
+ bin_max = bin_mid;
+ int start = Math.max(1, loc - bin_mid + 1);
+ int finish = Math.min(loc + bin_mid, text.length()) + pattern.length();
+
+ int[] rd = new int[finish + 2];
+ rd[finish + 1] = (1 << d) - 1;
+ for (int j = finish; j >= start; j--) {
+ int charMatch;
+ if (text.length() <= j - 1 || !s.containsKey(text.charAt(j - 1))) {
+ // Out of range.
+ charMatch = 0;
+ } else {
+ charMatch = s.get(text.charAt(j - 1));
+ }
+ if (d == 0) {
+ // First pass: exact match.
+ rd[j] = ((rd[j + 1] << 1) | 1) & charMatch;
+ } else {
+ // Subsequent passes: fuzzy match.
+ rd[j] = (((rd[j + 1] << 1) | 1) & charMatch)
+ | (((last_rd[j + 1] | last_rd[j]) << 1) | 1) | last_rd[j + 1];
+ }
+ if ((rd[j] & matchmask) != 0) {
+ double score = matchBitapScore(d, j - 1, loc, pattern);
+ // This match will almost certainly be better than any existing
+ // match. But check anyway.
+ if (score <= score_threshold) {
+ // Told you so.
+ score_threshold = score;
+ best_loc = j - 1;
+ if (best_loc > loc) {
+ // When passing loc, don't exceed our current distance from loc.
+ start = Math.max(1, 2 * loc - best_loc);
+ } else {
+ // Already passed loc, downhill from here on in.
+ break;
+ }
+ }
+ }
+ }
+ if (matchBitapScore(d + 1, loc, loc, pattern) > score_threshold) {
+ // No hope for a (better) match at greater error levels.
+ break;
+ }
+ last_rd = rd;
+ }
+ return best_loc;
+ }
+
+ /**
+ * Compute and return the score for a match with e errors and x location.
+ *
+ * @param e Number of errors in match.
+ * @param x Location of match.
+ * @param loc Expected location of match.
+ * @param pattern Pattern being sought.
+ * @return Overall score for match (0.0 = good, 1.0 = bad).
+ */
+ private double matchBitapScore(int e, int x, int loc, String pattern) {
+ float accuracy = (float) e / pattern.length();
+ int proximity = Math.abs(loc - x);
+ if (Match_Distance == 0) {
+ // Dodge divide by zero error.
+ return proximity == 0 ? accuracy : 1.0;
+ }
+ return accuracy + (proximity / (float) Match_Distance);
+ }
+
+ /**
+ * Initialise the alphabet for the Bitap algorithm.
+ *
+ * @param pattern The text to encode.
+ * @return Hash of character locations.
+ */
+ protected Map matchAlphabet(String pattern) {
+ Map s = new HashMap();
+ char[] char_pattern = pattern.toCharArray();
+ for (char c : char_pattern) {
+ s.put(c, 0);
+ }
+ int i = 0;
+ for (char c : char_pattern) {
+ s.put(c, s.get(c) | (1 << (pattern.length() - i - 1)));
+ i++;
+ }
+ return s;
+ }
+
+
+ // PATCH FUNCTIONS
+
+
+ /**
+ * Increase the context until it is unique,
+ * but don't let the pattern expand beyond Match_MaxBits.
+ *
+ * @param patch The patch to grow.
+ * @param text Source text.
+ */
+ protected void patch_addContext(Patch patch, String text) {
+ if (text.length() == 0) {
+ return;
+ }
+ String pattern = text.substring(patch.start2, patch.start2 + patch.length1);
+ int padding = 0;
+
+ // Look for the first and last matches of pattern in text. If two different
+ // matches are found, increase the pattern length.
+ while (text.indexOf(pattern) != text.lastIndexOf(pattern)
+ && pattern.length() < Match_MaxBits - Patch_Margin - Patch_Margin) {
+ padding += Patch_Margin;
+ pattern = text.substring(Math.max(0, patch.start2 - padding),
+ Math.min(text.length(), patch.start2 + patch.length1 + padding));
+ }
+ // Add one chunk for good luck.
+ padding += Patch_Margin;
+
+ // Add the prefix.
+ String prefix = text.substring(Math.max(0, patch.start2 - padding),
+ patch.start2);
+ if (prefix.length() != 0) {
+ patch.diffs.addFirst(new Diff(Operation.EQUAL, prefix));
+ }
+ // Add the suffix.
+ String suffix = text.substring(patch.start2 + patch.length1,
+ Math.min(text.length(), patch.start2 + patch.length1 + padding));
+ if (suffix.length() != 0) {
+ patch.diffs.addLast(new Diff(Operation.EQUAL, suffix));
+ }
+
+ // Roll back the start points.
+ patch.start1 -= prefix.length();
+ patch.start2 -= prefix.length();
+ // Extend the lengths.
+ patch.length1 += prefix.length() + suffix.length();
+ patch.length2 += prefix.length() + suffix.length();
+ }
+
+ /**
+ * Compute a list of patches to turn text1 into text2.
+ * A set of diffs will be computed.
+ *
+ * @param text1 Old text.
+ * @param text2 New text.
+ * @return LinkedList of Patch objects.
+ */
+ public LinkedList patchMake(String text1, String text2) {
+ if (text1 == null || text2 == null) {
+ throw new IllegalArgumentException("Null inputs. (patchMake)");
+ }
+ // No diffs provided, compute our own.
+ LinkedList diffs = diffMain(text1, text2, true);
+ if (diffs.size() > 2) {
+ diffCleanupSemantic(diffs);
+ diffCleanupEfficiency(diffs);
+ }
+ return patchMake(text1, diffs);
+ }
+
+ /**
+ * Compute a list of patches to turn text1 into text2.
+ * text1 will be derived from the provided diffs.
+ *
+ * @param diffs Array of Diff objects for text1 to text2.
+ * @return LinkedList of Patch objects.
+ */
+ public LinkedList patchMake(LinkedList diffs) {
+ if (diffs == null) {
+ throw new IllegalArgumentException("Null inputs. (patchMake)");
+ }
+ // No origin string provided, compute our own.
+ String text1 = diffText1(diffs);
+ return patchMake(text1, diffs);
+ }
+
+ /**
+ * Compute a list of patches to turn text1 into text2.
+ * text2 is ignored, diffs are the delta between text1 and text2.
+ *
+ * @param text1 Old text
+ * @param text2 Ignored.
+ * @param diffs Array of Diff objects for text1 to text2.
+ * @return LinkedList of Patch objects.
+ * @deprecated Prefer patchMake(String text1, LinkedList diffs).
+ */
+ public LinkedList patchMake(String text1, String text2,
+ LinkedList diffs) {
+ return patchMake(text1, diffs);
+ }
+
+ /**
+ * Compute a list of patches to turn text1 into text2.
+ * text2 is not provided, diffs are the delta between text1 and text2.
+ *
+ * @param text1 Old text.
+ * @param diffs Array of Diff objects for text1 to text2.
+ * @return LinkedList of Patch objects.
+ */
+ public LinkedList patchMake(String text1, LinkedList diffs) {
+ if (text1 == null || diffs == null) {
+ throw new IllegalArgumentException("Null inputs. (patchMake)");
+ }
+
+ LinkedList patches = new LinkedList();
+ if (diffs.isEmpty()) {
+ return patches; // Get rid of the null case.
+ }
+ Patch patch = new Patch();
+ int char_count1 = 0; // Number of characters into the text1 string.
+ int char_count2 = 0; // Number of characters into the text2 string.
+ // Start with text1 (prepatch_text) and apply the diffs until we arrive at
+ // text2 (postpatch_text). We recreate the patches one by one to determine
+ // context info.
+ String prepatch_text = text1;
+ String postpatch_text = text1;
+ for (Diff aDiff : diffs) {
+ if (patch.diffs.isEmpty() && aDiff.operation != Operation.EQUAL) {
+ // A new patch starts here.
+ patch.start1 = char_count1;
+ patch.start2 = char_count2;
+ }
+
+ switch (aDiff.operation) {
+ case INSERT:
+ patch.diffs.add(aDiff);
+ patch.length2 += aDiff.text.length();
+ postpatch_text = postpatch_text.substring(0, char_count2)
+ + aDiff.text + postpatch_text.substring(char_count2);
+ break;
+ case DELETE:
+ patch.length1 += aDiff.text.length();
+ patch.diffs.add(aDiff);
+ postpatch_text = postpatch_text.substring(0, char_count2)
+ + postpatch_text.substring(char_count2 + aDiff.text.length());
+ break;
+ case EQUAL:
+ if (aDiff.text.length() <= 2 * Patch_Margin
+ && !patch.diffs.isEmpty() && aDiff != diffs.getLast()) {
+ // Small equality inside a patch.
+ patch.diffs.add(aDiff);
+ patch.length1 += aDiff.text.length();
+ patch.length2 += aDiff.text.length();
+ }
+
+ if (aDiff.text.length() >= 2 * Patch_Margin) {
+ // Time for a new patch.
+ if (!patch.diffs.isEmpty()) {
+ patch_addContext(patch, prepatch_text);
+ patches.add(patch);
+ patch = new Patch();
+ // Unlike Unidiff, our patch lists have a rolling context.
+ // http://code.google.com/p/google-diff-match-patch/wiki/Unidiff
+ // Update prepatch text & pos to reflect the application of the
+ // just completed patch.
+ prepatch_text = postpatch_text;
+ char_count1 = char_count2;
+ }
+ }
+ break;
+ }
+
+ // Update the current character count.
+ if (aDiff.operation != Operation.INSERT) {
+ char_count1 += aDiff.text.length();
+ }
+ if (aDiff.operation != Operation.DELETE) {
+ char_count2 += aDiff.text.length();
+ }
+ }
+ // Pick up the leftover patch if not empty.
+ if (!patch.diffs.isEmpty()) {
+ patch_addContext(patch, prepatch_text);
+ patches.add(patch);
+ }
+
+ return patches;
+ }
+
+ /**
+ * Given an array of patches, return another array that is identical.
+ *
+ * @param patches Array of Patch objects.
+ * @return Array of Patch objects.
+ */
+ public LinkedList patchDeepCopy(LinkedList patches) {
+ LinkedList patchesCopy = new LinkedList();
+ for (Patch aPatch : patches) {
+ Patch patchCopy = new Patch();
+ for (Diff aDiff : aPatch.diffs) {
+ Diff diffCopy = new Diff(aDiff.operation, aDiff.text);
+ patchCopy.diffs.add(diffCopy);
+ }
+ patchCopy.start1 = aPatch.start1;
+ patchCopy.start2 = aPatch.start2;
+ patchCopy.length1 = aPatch.length1;
+ patchCopy.length2 = aPatch.length2;
+ patchesCopy.add(patchCopy);
+ }
+ return patchesCopy;
+ }
+
+ /**
+ * Merge a set of patches onto the text. Return a patched text, as well
+ * as an array of true/false values indicating which patches were applied.
+ *
+ * @param patches Array of Patch objects
+ * @param text Old text.
+ * @return Two element Object array, containing the new text and an array of
+ * boolean values.
+ */
+ public Object[] patchApply(LinkedList patches, String text) {
+ if (patches.isEmpty()) {
+ return new Object[]{text, new boolean[0]};
+ }
+
+ // Deep copy the patches so that no changes are made to originals.
+ patches = patchDeepCopy(patches);
+
+ String nullPadding = patchAddPadding(patches);
+ text = nullPadding + text + nullPadding;
+ patchSplitMax(patches);
+
+ int x = 0;
+ // delta keeps track of the offset between the expected and actual location
+ // of the previous patch. If there are patches expected at positions 10 and
+ // 20, but the first patch was found at 12, delta is 2 and the second patch
+ // has an effective expected position of 22.
+ int delta = 0;
+ boolean[] results = new boolean[patches.size()];
+ for (Patch aPatch : patches) {
+ int expected_loc = aPatch.start2 + delta;
+ String text1 = diffText1(aPatch.diffs);
+ int start_loc;
+ int end_loc = -1;
+ if (text1.length() > this.Match_MaxBits) {
+ // patchSplitMax will only provide an oversized pattern in the case of
+ // a monster delete.
+ start_loc = matchMain(text,
+ text1.substring(0, this.Match_MaxBits), expected_loc);
+ if (start_loc != -1) {
+ end_loc = matchMain(text,
+ text1.substring(text1.length() - this.Match_MaxBits),
+ expected_loc + text1.length() - this.Match_MaxBits);
+ if (end_loc == -1 || start_loc >= end_loc) {
+ // Can't find valid trailing context. Drop this patch.
+ start_loc = -1;
+ }
+ }
+ } else {
+ start_loc = matchMain(text, text1, expected_loc);
+ }
+ if (start_loc == -1) {
+ // No match found. :(
+ results[x] = false;
+ // Subtract the delta for this failed patch from subsequent patches.
+ delta -= aPatch.length2 - aPatch.length1;
+ } else {
+ // Found a match. :)
+ results[x] = true;
+ delta = start_loc - expected_loc;
+ String text2;
+ if (end_loc == -1) {
+ text2 = text.substring(start_loc,
+ Math.min(start_loc + text1.length(), text.length()));
+ } else {
+ text2 = text.substring(start_loc,
+ Math.min(end_loc + this.Match_MaxBits, text.length()));
+ }
+ if (text1.equals(text2)) {
+ // Perfect match, just shove the replacement text in.
+ text = text.substring(0, start_loc) + diffText2(aPatch.diffs)
+ + text.substring(start_loc + text1.length());
+ } else {
+ // Imperfect match. Run a diff to get a framework of equivalent
+ // indices.
+ LinkedList diffs = diffMain(text1, text2, false);
+ if (text1.length() > this.Match_MaxBits
+ && diffLevenshtein(diffs) / (float) text1.length()
+ > this.Patch_DeleteThreshold) {
+ // The end points match, but the content is unacceptably bad.
+ results[x] = false;
+ } else {
+ diffCleanupSemanticLossless(diffs);
+ int index1 = 0;
+ for (Diff aDiff : aPatch.diffs) {
+ if (aDiff.operation != Operation.EQUAL) {
+ int index2 = diffXIndex(diffs, index1);
+ if (aDiff.operation == Operation.INSERT) {
+ // Insertion
+ text = text.substring(0, start_loc + index2) + aDiff.text
+ + text.substring(start_loc + index2);
+ } else if (aDiff.operation == Operation.DELETE) {
+ // Deletion
+ text = text.substring(0, start_loc + index2)
+ + text.substring(start_loc + diffXIndex(diffs,
+ index1 + aDiff.text.length()));
+ }
+ }
+ if (aDiff.operation != Operation.DELETE) {
+ index1 += aDiff.text.length();
+ }
+ }
+ }
+ }
+ }
+ x++;
+ }
+ // Strip the padding off.
+ text = text.substring(nullPadding.length(), text.length()
+ - nullPadding.length());
+ return new Object[]{text, results};
+ }
+
+ /**
+ * Add some padding on text start and end so that edges can match something.
+ * Intended to be called only from within patchApply.
+ *
+ * @param patches Array of Patch objects.
+ * @return The padding string added to each side.
+ */
+ public String patchAddPadding(LinkedList patches) {
+ short paddingLength = this.Patch_Margin;
+ String nullPadding = "";
+ StringBuilder buffer = new StringBuilder();
+ for (short x = 1; x <= paddingLength; x++) {
+ buffer.append(String.valueOf((char) x));
+ }
+ nullPadding = buffer.toString();
+
+ // Bump all the patches forward.
+ for (Patch aPatch : patches) {
+ aPatch.start1 += paddingLength;
+ aPatch.start2 += paddingLength;
+ }
+
+ // Add some padding on start of first diff.
+ Patch patch = patches.getFirst();
+ LinkedList diffs = patch.diffs;
+ if (diffs.isEmpty() || diffs.getFirst().operation != Operation.EQUAL) {
+ // Add nullPadding equality.
+ diffs.addFirst(new Diff(Operation.EQUAL, nullPadding));
+ patch.start1 -= paddingLength; // Should be 0.
+ patch.start2 -= paddingLength; // Should be 0.
+ patch.length1 += paddingLength;
+ patch.length2 += paddingLength;
+ } else if (paddingLength > diffs.getFirst().text.length()) {
+ // Grow first equality.
+ Diff firstDiff = diffs.getFirst();
+ int extraLength = paddingLength - firstDiff.text.length();
+ firstDiff.text = nullPadding.substring(firstDiff.text.length())
+ + firstDiff.text;
+ patch.start1 -= extraLength;
+ patch.start2 -= extraLength;
+ patch.length1 += extraLength;
+ patch.length2 += extraLength;
+ }
+
+ // Add some padding on end of last diff.
+ patch = patches.getLast();
+ diffs = patch.diffs;
+ if (diffs.isEmpty() || diffs.getLast().operation != Operation.EQUAL) {
+ // Add nullPadding equality.
+ diffs.addLast(new Diff(Operation.EQUAL, nullPadding));
+ patch.length1 += paddingLength;
+ patch.length2 += paddingLength;
+ } else if (paddingLength > diffs.getLast().text.length()) {
+ // Grow last equality.
+ Diff lastDiff = diffs.getLast();
+ int extraLength = paddingLength - lastDiff.text.length();
+ lastDiff.text += nullPadding.substring(0, extraLength);
+ patch.length1 += extraLength;
+ patch.length2 += extraLength;
+ }
+
+ return nullPadding;
+ }
+
+ /**
+ * Look through the patches and break up any which are longer than the
+ * maximum limit of the match algorithm.
+ * Intended to be called only from within patchApply.
+ *
+ * @param patches LinkedList of Patch objects.
+ */
+ public void patchSplitMax(LinkedList patches) {
+ short patch_size = Match_MaxBits;
+ String precontext, postcontext;
+ Patch patch;
+ int start1, start2;
+ boolean empty;
+ Operation diff_type;
+ String diff_text;
+ ListIterator pointer = patches.listIterator();
+ Patch bigpatch = pointer.hasNext() ? pointer.next() : null;
+ while (bigpatch != null) {
+ if (bigpatch.length1 <= Match_MaxBits) {
+ bigpatch = pointer.hasNext() ? pointer.next() : null;
+ continue;
+ }
+ // Remove the big old patch.
+ pointer.remove();
+ start1 = bigpatch.start1;
+ start2 = bigpatch.start2;
+ precontext = "";
+ while (!bigpatch.diffs.isEmpty()) {
+ // Create one of several smaller patches.
+ patch = new Patch();
+ empty = true;
+ patch.start1 = start1 - precontext.length();
+ patch.start2 = start2 - precontext.length();
+ if (precontext.length() != 0) {
+ patch.length1 = patch.length2 = precontext.length();
+ patch.diffs.add(new Diff(Operation.EQUAL, precontext));
+ }
+ while (!bigpatch.diffs.isEmpty()
+ && patch.length1 < patch_size - Patch_Margin) {
+ diff_type = bigpatch.diffs.getFirst().operation;
+ diff_text = bigpatch.diffs.getFirst().text;
+ if (diff_type == Operation.INSERT) {
+ // Insertions are harmless.
+ patch.length2 += diff_text.length();
+ start2 += diff_text.length();
+ patch.diffs.addLast(bigpatch.diffs.removeFirst());
+ empty = false;
+ } else if (diff_type == Operation.DELETE && patch.diffs.size() == 1
+ && patch.diffs.getFirst().operation == Operation.EQUAL
+ && diff_text.length() > 2 * patch_size) {
+ // This is a large deletion. Let it pass in one chunk.
+ patch.length1 += diff_text.length();
+ start1 += diff_text.length();
+ empty = false;
+ patch.diffs.add(new Diff(diff_type, diff_text));
+ bigpatch.diffs.removeFirst();
+ } else {
+ // Deletion or equality. Only take as much as we can stomach.
+ diff_text = diff_text.substring(0, Math.min(diff_text.length(),
+ patch_size - patch.length1 - Patch_Margin));
+ patch.length1 += diff_text.length();
+ start1 += diff_text.length();
+ if (diff_type == Operation.EQUAL) {
+ patch.length2 += diff_text.length();
+ start2 += diff_text.length();
+ } else {
+ empty = false;
+ }
+ patch.diffs.add(new Diff(diff_type, diff_text));
+ if (diff_text.equals(bigpatch.diffs.getFirst().text)) {
+ bigpatch.diffs.removeFirst();
+ } else {
+ bigpatch.diffs.getFirst().text = bigpatch.diffs.getFirst().text
+ .substring(diff_text.length());
+ }
+ }
+ }
+ // Compute the head context for the next patch.
+ precontext = diffText2(patch.diffs);
+ precontext = precontext.substring(Math.max(0, precontext.length()
+ - Patch_Margin));
+ // Append the end context for this patch.
+ if (diffText1(bigpatch.diffs).length() > Patch_Margin) {
+ postcontext = diffText1(bigpatch.diffs).substring(0, Patch_Margin);
+ } else {
+ postcontext = diffText1(bigpatch.diffs);
+ }
+ if (postcontext.length() != 0) {
+ patch.length1 += postcontext.length();
+ patch.length2 += postcontext.length();
+ if (!patch.diffs.isEmpty()
+ && patch.diffs.getLast().operation == Operation.EQUAL) {
+ patch.diffs.getLast().text += postcontext;
+ } else {
+ patch.diffs.add(new Diff(Operation.EQUAL, postcontext));
+ }
+ }
+ if (!empty) {
+ pointer.add(patch);
+ }
+ }
+ bigpatch = pointer.hasNext() ? pointer.next() : null;
+ }
+ }
+
+ /**
+ * Take a list of patches and return a textual representation.
+ *
+ * @param patches List of Patch objects.
+ * @return Text representation of patches.
+ */
+ public String patchToText(List patches) {
+ StringBuilder text = new StringBuilder();
+ for (Patch aPatch : patches) {
+ text.append(aPatch);
+ }
+ return text.toString();
+ }
+
+ /**
+ * Parse a textual representation of patches and return a List of Patch
+ * objects.
+ *
+ * @param textline Text representation of patches.
+ * @return List of Patch objects.
+ * @throws IllegalArgumentException If invalid input.
+ */
+ public List patchFromText(String textline)
+ throws IllegalArgumentException {
+ List patches = new LinkedList();
+ if (textline.length() == 0) {
+ return patches;
+ }
+ List textList = Arrays.asList(textline.split("\n"));
+ LinkedList text = new LinkedList(textList);
+ Patch patch;
+ Pattern patchHeader
+ = Pattern.compile("^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$");
+ Matcher m;
+ char sign;
+ String line;
+ while (!text.isEmpty()) {
+ m = patchHeader.matcher(text.getFirst());
+ if (!m.matches()) {
+ throw new IllegalArgumentException(
+ "Invalid patch string: " + text.getFirst());
+ }
+ patch = new Patch();
+ patches.add(patch);
+ patch.start1 = Integer.parseInt(m.group(1));
+ if (m.group(2).length() == 0) {
+ patch.start1--;
+ patch.length1 = 1;
+ } else if (m.group(2).equals("0")) {
+ patch.length1 = 0;
+ } else {
+ patch.start1--;
+ patch.length1 = Integer.parseInt(m.group(2));
+ }
+
+ patch.start2 = Integer.parseInt(m.group(3));
+ if (m.group(4).length() == 0) {
+ patch.start2--;
+ patch.length2 = 1;
+ } else if (m.group(4).equals("0")) {
+ patch.length2 = 0;
+ } else {
+ patch.start2--;
+ patch.length2 = Integer.parseInt(m.group(4));
+ }
+ text.removeFirst();
+
+ while (!text.isEmpty()) {
+ try {
+ sign = text.getFirst().charAt(0);
+ } catch (IndexOutOfBoundsException e) {
+ // Blank line? Whatever.
+ text.removeFirst();
+ continue;
+ }
+ line = text.getFirst().substring(1);
+ line = line.replace("+", "%2B"); // decode would change all "+" to " "
+ try {
+ line = URLDecoder.decode(line, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // Not likely on modern system.
+ throw new Error("This system does not support UTF-8.", e);
+ } catch (IllegalArgumentException e) {
+ // Malformed URI sequence.
+ throw new IllegalArgumentException(
+ "Illegal escape in patchFromText: " + line, e);
+ }
+ if (sign == '-') {
+ // Deletion.
+ patch.diffs.add(new Diff(Operation.DELETE, line));
+ } else if (sign == '+') {
+ // Insertion.
+ patch.diffs.add(new Diff(Operation.INSERT, line));
+ } else if (sign == ' ') {
+ // Minor equality.
+ patch.diffs.add(new Diff(Operation.EQUAL, line));
+ } else if (sign == '@') {
+ // Start of next patch.
+ break;
+ } else {
+ // WTF?
+ throw new IllegalArgumentException(
+ "Invalid patch mode '" + sign + "' in: " + line);
+ }
+ text.removeFirst();
+ }
+ }
+ return patches;
+ }
+
+
+}
diff --git a/atf-application/src/main/java/ru/bsc/test/autotester/launcher/impl/LaunchResult.java b/atf-application/src/main/java/ru/bsc/test/autotester/launcher/impl/LaunchResult.java
index 5481feb8..abf510d2 100644
--- a/atf-application/src/main/java/ru/bsc/test/autotester/launcher/impl/LaunchResult.java
+++ b/atf-application/src/main/java/ru/bsc/test/autotester/launcher/impl/LaunchResult.java
@@ -1,48 +1,51 @@
-/*
- * Copyright 2018 BSC Msc, LLC
- *
- * This file is part of the AuTe Framework project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ru.bsc.test.autotester.launcher.impl;
-
-import lombok.ToString;
-import ru.bsc.test.at.executor.model.ScenarioResult;
-
-import java.util.List;
-
-/**
- * @author Pavel Golovkin
- */
-@ToString
-public class LaunchResult {
-
- private int failedTestsCount;
- private int passedTestsCount;
-
- public boolean isFailed() {
- return failedTestsCount > 0;
- }
-
- LaunchResult(List scenarioResults) {
- for (ScenarioResult scenarioResult: scenarioResults) {
- if (scenarioResult.isFailed()) {
- failedTestsCount++;
- } else {
- passedTestsCount++;
- }
- }
- }
-}
+/*
+ * Copyright 2018 BSC Msc, LLC
+ *
+ * This file is part of the AuTe Framework project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ru.bsc.test.autotester.launcher.impl;
+
+import ru.bsc.test.at.executor.model.ScenarioResult;
+
+import java.util.List;
+
+/**
+ * @author Pavel Golovkin
+ */
+public class LaunchResult {
+
+ private int failedTestsCount;
+ private int passedTestsCount;
+
+ public boolean isFailed() {
+ return failedTestsCount > 0;
+ }
+
+ LaunchResult(List scenarioResults) {
+ for (ScenarioResult scenarioResult: scenarioResults) {
+ if (scenarioResult.isFailed()) {
+ failedTestsCount++;
+ } else {
+ passedTestsCount++;
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "LaunchResult{" + "failedTestsCount=" + failedTestsCount + ", passedTestsCount=" + passedTestsCount + '}';
+ }
+}
diff --git a/atf-application/src/main/java/ru/bsc/test/autotester/mapper/ExecutionResultRoMapper.java b/atf-application/src/main/java/ru/bsc/test/autotester/mapper/ExecutionResultRoMapper.java
index 40bccf06..ff111ee5 100644
--- a/atf-application/src/main/java/ru/bsc/test/autotester/mapper/ExecutionResultRoMapper.java
+++ b/atf-application/src/main/java/ru/bsc/test/autotester/mapper/ExecutionResultRoMapper.java
@@ -1,90 +1,90 @@
-/*
- * Copyright 2018 BSC Msc, LLC
- *
- * This file is part of the AuTe Framework project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ru.bsc.test.autotester.mapper;
-
-import org.mapstruct.Mapper;
-import org.mapstruct.Mapping;
-import org.mapstruct.Mappings;
-import org.springframework.beans.factory.annotation.Autowired;
-import ru.bsc.test.at.executor.model.Step;
-import ru.bsc.test.autotester.component.JsonDiffCalculator;
-import ru.bsc.test.autotester.model.ExecutionResult;
-import ru.bsc.test.autotester.ro.ExecutionResultRo;
-import ru.bsc.test.autotester.ro.ScenarioResultRo;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-@Mapper(config = Config.class)
-public abstract class ExecutionResultRoMapper {
-
- @Autowired
- private JsonDiffCalculator diffCalculator;
- @Autowired
- private ProjectRoMapper projectRoMapper;
- @Autowired
- private StepRoMapper stepRoMapper;
-
- @Mappings({
- @Mapping(target = "finished", source = "finished"),
- @Mapping(target = "scenarioResultList", ignore = true),
- })
-
- /* default */ abstract ExecutionResultRo executionResultToRo(ExecutionResult executionResult);
-
- public ExecutionResultRo map(ExecutionResult executionResult) {
- ExecutionResultRo executionResultRo = executionResultToRo(executionResult);
- if (executionResultRo == null) {
- return null;
- }
-
- if (executionResult != null) {
- List scenarioResultList = executionResult.getScenarioResults()
- .stream()
- .map(scenarioListEntry -> ScenarioResultRo.builder()
- .scenario(projectRoMapper.scenarioToScenarioRo("", scenarioListEntry.getScenario()))
- .stepResultList(stepRoMapper.convertStepResultListToStepResultRo(scenarioListEntry.getStepResultList()))
- .totalSteps(scenarioListEntry
- .getScenario()
- .getStepList()
- .stream()
- .filter(step -> !step.getDisabled())
- .map(Step::getStepParameterSetList)
- .mapToInt(list -> list != null ? (list.size() == 0 ? 1 : list.size()) : 1)
- .sum()
- ).build()
- )
- .collect(Collectors.toList());
- executionResultRo.setScenarioResultList(scenarioResultList);
- }
-
- addDiffToResult(executionResultRo);
- return executionResultRo;
- }
-
- private void addDiffToResult(ExecutionResultRo executionResultRo) {
- if (executionResultRo.getScenarioResultList() == null) {
- return;
- }
- executionResultRo.getScenarioResultList().stream()
- .map(ScenarioResultRo::getStepResultList)
- .flatMap(List::stream)
- .forEach(result -> result.setDiff(diffCalculator.calculate(result.getActual(), result.getExpected())));
- }
-}
+/*
+ * Copyright 2018 BSC Msc, LLC
+ *
+ * This file is part of the AuTe Framework project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ru.bsc.test.autotester.mapper;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
+import org.springframework.beans.factory.annotation.Autowired;
+import ru.bsc.test.at.executor.model.Step;
+import ru.bsc.test.autotester.component.JsonDiffCalculator;
+import ru.bsc.test.autotester.model.ExecutionResult;
+import ru.bsc.test.autotester.ro.ExecutionResultRo;
+import ru.bsc.test.autotester.ro.ScenarioResultRo;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Mapper(config = Config.class)
+public abstract class ExecutionResultRoMapper {
+
+ @Autowired
+ private JsonDiffCalculator diffCalculator;
+ @Autowired
+ private ProjectRoMapper projectRoMapper;
+ @Autowired
+ private StepRoMapper stepRoMapper;
+
+ @Mappings({
+ @Mapping(target = "finished", source = "finished"),
+ @Mapping(target = "scenarioResultList", ignore = true),
+ })
+
+ /* default */ abstract ExecutionResultRo executionResultToRo(ExecutionResult executionResult);
+
+ public ExecutionResultRo map(ExecutionResult executionResult) {
+ ExecutionResultRo executionResultRo = executionResultToRo(executionResult);
+ if (executionResultRo == null) {
+ return null;
+ }
+
+ if (executionResult != null) {
+ List scenarioResultList = executionResult.getScenarioResults()
+ .stream()
+ .map(scenarioListEntry -> ScenarioResultRo.builder()
+ .scenario(projectRoMapper.scenarioToScenarioRo("", scenarioListEntry.getScenario()))
+ .stepResultList(stepRoMapper.convertStepResultListToStepResultRo(scenarioListEntry.getStepResultList()))
+ .totalSteps(scenarioListEntry
+ .getScenario()
+ .getStepList()
+ .stream()
+ .filter(step -> !step.getDisabled())
+ .map(Step::getStepParameterSetList)
+ .mapToInt(list -> list != null ? list.size() == 0 ? 1 : list.size() : 1)
+ .sum()
+ ).build()
+ )
+ .collect(Collectors.toList());
+ executionResultRo.setScenarioResultList(scenarioResultList);
+ }
+
+ addDiffToResult(executionResultRo);
+ return executionResultRo;
+ }
+
+ private void addDiffToResult(ExecutionResultRo executionResultRo) {
+ if (executionResultRo.getScenarioResultList() == null) {
+ return;
+ }
+ executionResultRo.getScenarioResultList().stream()
+ .map(ScenarioResultRo::getStepResultList)
+ .flatMap(List::stream)
+ .forEach(result -> result.setDiff(diffCalculator.calculate(result.getActual(), result.getExpected())));
+ }
+}
diff --git a/atf-application/src/main/java/ru/bsc/test/autotester/report/impl/allure/AllureReportGenerator.java b/atf-application/src/main/java/ru/bsc/test/autotester/report/impl/allure/AllureReportGenerator.java
index f5e41c07..ab063bb3 100644
--- a/atf-application/src/main/java/ru/bsc/test/autotester/report/impl/allure/AllureReportGenerator.java
+++ b/atf-application/src/main/java/ru/bsc/test/autotester/report/impl/allure/AllureReportGenerator.java
@@ -62,9 +62,8 @@
import ru.yandex.qatools.allure.model.TestCaseResult;
import ru.yandex.qatools.allure.model.TestSuiteResult;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
+import java.io.*;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -114,7 +113,8 @@ public synchronized void generate(File directory) throws Exception {
}
for (AllurePreparedData data : buildReportData(resultDirectory, getScenarioStepResultMap())) {
- try (FileWriter writer = new FileWriter(data.getDataFile())) {
+ try (FileOutputStream fileStream = new FileOutputStream(data.getDataFile());
+ OutputStreamWriter writer = new OutputStreamWriter(fileStream, StandardCharsets.UTF_8)) {
gson.toJson(data.getSuiteResult(), writer);
} catch (IOException e) {
log.error("Could not convert testSuiteResult {} to json", data, e);
diff --git a/atf-application/src/main/java/ru/bsc/test/autotester/repository/yaml/YamlScenarioRepositoryImpl.java b/atf-application/src/main/java/ru/bsc/test/autotester/repository/yaml/YamlScenarioRepositoryImpl.java
index 77fb928b..3f03386c 100644
--- a/atf-application/src/main/java/ru/bsc/test/autotester/repository/yaml/YamlScenarioRepositoryImpl.java
+++ b/atf-application/src/main/java/ru/bsc/test/autotester/repository/yaml/YamlScenarioRepositoryImpl.java
@@ -1,198 +1,197 @@
-/*
- * Copyright 2018 BSC Msc, LLC
- *
- * This file is part of the AuTe Framework project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ru.bsc.test.autotester.repository.yaml;
-
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Repository;
-import org.yaml.snakeyaml.reader.ReaderException;
-import ru.bsc.test.at.executor.model.Scenario;
-import ru.bsc.test.autotester.component.Translator;
-import ru.bsc.test.autotester.properties.EnvironmentProperties;
-import ru.bsc.test.autotester.repository.ScenarioRepository;
-import ru.bsc.test.autotester.repository.yaml.base.BaseYamlRepository;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.FileVisitOption;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.*;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static org.apache.commons.lang3.StringUtils.containsIgnoreCase;
-
-/**
- * Created by sdoroshin on 27.10.2017.
- *
- */
-
-@Repository
-@Slf4j
-public class YamlScenarioRepositoryImpl extends BaseYamlRepository implements ScenarioRepository {
-
- private final String projectsPath;
-
- @Autowired
- public YamlScenarioRepositoryImpl(EnvironmentProperties environmentProperties, Translator translator) {
- super(translator);
- this.projectsPath = environmentProperties.getProjectsDirectoryPath();
- }
-
- @Override
- public List findScenarios(String projectCode) {
- return findScenarios(projectCode, false);
- }
-
- @Override
- public List findScenariosWithSteps(String projectCode) {
- return findScenarios(projectCode, true);
- }
-
- @Override
- public Scenario findScenario(String projectCode, String scenarioPath) throws IOException {
- String[] pathParts = scenarioPath.split("/");
- return loadScenarioFromFiles(
- Paths.get(projectsPath, projectCode, "scenarios", scenarioPath).toFile(),
- pathParts.length > 1 ? pathParts[0] : null,
- true
- );
- }
-
- @Override
- public Scenario saveScenario(String projectCode, String scenarioPath, Scenario data, boolean updateDirectoryName) throws IOException {
- if (StringUtils.isBlank(data.getName())) {
- throw new IOException("Empty scenario name");
- }
-
- String newCode = updateDirectoryName ? translator.translate(data.getName()) : data.getCode();
- log.info("newCode: {}", newCode);
- String newScenarioPath = getScenarioPath(data.getScenarioGroup(), newCode);
- log.info("newScenarioPath: {}", newScenarioPath);
- if (scenarioPath != null) {
- String[] pathParts = scenarioPath.split("/");
- String codePart = pathParts.length > 1 ? pathParts[1] : pathParts[0];
- String groupPart = pathParts.length > 1 ? pathParts[0] : null;
- String oldScenarioPath = getScenarioPath(groupPart, codePart);
-
- if (!Objects.equals(newScenarioPath, oldScenarioPath)) {
- if (Paths.get(projectsPath, projectCode, "scenarios", newScenarioPath).toFile().exists()) {
- throw new IOException("Directory already exists");
- }
- }
-
- Path path = Paths.get(projectsPath, projectCode, "scenarios", scenarioPath);
- if (Files.exists(path)) {
- File renamed = new File(path.toFile().getParentFile(), data.getCode() + "-" + UUID.randomUUID().toString());
- boolean canRemove = path.toFile().renameTo(renamed);
- if (canRemove) {
- FileUtils.deleteDirectory(renamed);
- } else {
- throw new IOException("Old scenario directory not removed");
- }
- }
- } else {
- if (Paths.get(projectsPath, projectCode, "scenarios", newScenarioPath).toFile().exists()) {
- throw new IOException("Directory already exists");
- }
- }
-
- data.setCode(newCode);
- File scenarioFile = Paths.get(
- projectsPath,
- projectCode,
- "scenarios",
- newScenarioPath,
- SCENARIO_YML_FILENAME
- ).toFile();
-
- log.info("scenarioFile: {}", scenarioFile);
-
- File scenarioRootDirectory = scenarioFile.getParentFile();
- saveScenarioToFiles(data, scenarioFile);
- data.getStepList().forEach(step -> loadStepFromFiles(step, scenarioRootDirectory));
- return data;
- }
-
- private String getScenarioPath(String scenarioGroup, String code) {
- return StringUtils.isNotEmpty(scenarioGroup) ? scenarioGroup + "/" + code : code;
- }
-
- @Override
- public Set findByRelativeUrl(String projectCode, String relativeUrl) {
- return findScenariosWithSteps(projectCode).stream()
- .filter(scenario -> checkSteps(scenario, relativeUrl))
- .collect(Collectors.toSet());
- }
-
- @Override
- public void delete(String projectCode, String scenarioPath) throws IOException {
- Path scenarioDirectory = Paths.get(projectsPath, projectCode, "scenarios", scenarioPath);
- try (Stream filesStream = Files.walk(scenarioDirectory, FileVisitOption.FOLLOW_LINKS)) {
- filesStream.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
- }
- }
-
- private List findScenarios(String projectCode, boolean fetchSteps) {
- File scenariosDirectory = Paths.get(projectsPath, projectCode, "scenarios").toFile();
- if (!scenariosDirectory.exists()) {
- return Collections.emptyList();
- }
- File[] directories = scenariosDirectory.listFiles(File::isDirectory);
- if (directories == null) {
- return Collections.emptyList();
- }
- List scenarios = new ArrayList<>();
- for (File directory : directories) {
- File scenarioYml = new File(directory, SCENARIO_YML_FILENAME);
- if (scenarioYml.exists()) {
- try {
- scenarios.add(loadScenarioFromFiles(directory, null, fetchSteps));
- } catch (IOException e) {
- log.error("Read file " + scenarioYml.getAbsolutePath(), e);
- }
- } else {
- File[] innerFileList = directory.listFiles(File::isDirectory);
- if (innerFileList != null) {
- for (File scenarioYmlInGroup : innerFileList) {
- if (new File(scenarioYmlInGroup, SCENARIO_YML_FILENAME).exists()) {
- try {
- scenarios.add(loadScenarioFromFiles(scenarioYmlInGroup, directory.getName(), fetchSteps));
- } catch (IOException | ReaderException e) {
- log.error("Read file {} {}", scenarioYmlInGroup, e);
- }
- }
- }
- }
- }
- }
- return scenarios;
- }
-
- private boolean checkSteps(Scenario scenario, String relativeUrl) {
- return scenario.getStepList()
- .stream()
- .anyMatch(s -> containsIgnoreCase(s.getRelativeUrl(), relativeUrl));
- }
-
-}
+/*
+ * Copyright 2018 BSC Msc, LLC
+ *
+ * This file is part of the AuTe Framework project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ru.bsc.test.autotester.repository.yaml;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+import org.yaml.snakeyaml.reader.ReaderException;
+import ru.bsc.test.at.executor.model.Scenario;
+import ru.bsc.test.autotester.component.Translator;
+import ru.bsc.test.autotester.properties.EnvironmentProperties;
+import ru.bsc.test.autotester.repository.ScenarioRepository;
+import ru.bsc.test.autotester.repository.yaml.base.BaseYamlRepository;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileVisitOption;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.apache.commons.lang3.StringUtils.containsIgnoreCase;
+
+/**
+ * Created by sdoroshin on 27.10.2017.
+ *
+ */
+
+@Repository
+@Slf4j
+public class YamlScenarioRepositoryImpl extends BaseYamlRepository implements ScenarioRepository {
+
+ private final String projectsPath;
+
+ @Autowired
+ public YamlScenarioRepositoryImpl(EnvironmentProperties environmentProperties, Translator translator) {
+ super(translator);
+ this.projectsPath = environmentProperties.getProjectsDirectoryPath();
+ }
+
+ @Override
+ public List findScenarios(String projectCode) {
+ return findScenarios(projectCode, false);
+ }
+
+ @Override
+ public List findScenariosWithSteps(String projectCode) {
+ return findScenarios(projectCode, true);
+ }
+
+ @Override
+ public Scenario findScenario(String projectCode, String scenarioPath) throws IOException {
+ String[] pathParts = scenarioPath.split("/");
+ return loadScenarioFromFiles(
+ Paths.get(projectsPath, projectCode, "scenarios", scenarioPath).toFile(),
+ pathParts.length > 1 ? pathParts[0] : null,
+ true
+ );
+ }
+
+ @Override
+ public Scenario saveScenario(String projectCode, String scenarioPath, Scenario data, boolean updateDirectoryName) throws IOException {
+ if (StringUtils.isBlank(data.getName())) {
+ throw new IOException("Empty scenario name");
+ }
+
+ String newCode = updateDirectoryName ? translator.translate(data.getName()) : data.getCode();
+ log.info("newCode: {}", newCode);
+ String newScenarioPath = getScenarioPath(data.getScenarioGroup(), newCode);
+ log.info("newScenarioPath: {}", newScenarioPath);
+ if (scenarioPath != null) {
+ String[] pathParts = scenarioPath.split("/");
+ String codePart = pathParts.length > 1 ? pathParts[1] : pathParts[0];
+ String groupPart = pathParts.length > 1 ? pathParts[0] : null;
+ String oldScenarioPath = getScenarioPath(groupPart, codePart);
+
+ if (!Objects.equals(newScenarioPath, oldScenarioPath) &&
+ Paths.get(projectsPath, projectCode, "scenarios", newScenarioPath).toFile().exists()) {
+ throw new IOException("Directory already exists");
+ }
+
+ Path path = Paths.get(projectsPath, projectCode, "scenarios", scenarioPath);
+ if (Files.exists(path)) {
+ File renamed = new File(path.toFile().getParentFile(), data.getCode() + "-" + UUID.randomUUID().toString());
+ boolean canRemove = path.toFile().renameTo(renamed);
+ if (canRemove) {
+ FileUtils.deleteDirectory(renamed);
+ } else {
+ throw new IOException("Old scenario directory not removed");
+ }
+ }
+ } else {
+ if (Paths.get(projectsPath, projectCode, "scenarios", newScenarioPath).toFile().exists()) {
+ throw new IOException("Directory already exists");
+ }
+ }
+
+ data.setCode(newCode);
+ File scenarioFile = Paths.get(
+ projectsPath,
+ projectCode,
+ "scenarios",
+ newScenarioPath,
+ SCENARIO_YML_FILENAME
+ ).toFile();
+
+ log.info("scenarioFile: {}", scenarioFile);
+
+ File scenarioRootDirectory = scenarioFile.getParentFile();
+ saveScenarioToFiles(data, scenarioFile);
+ data.getStepList().forEach(step -> loadStepFromFiles(step, scenarioRootDirectory));
+ return data;
+ }
+
+ private String getScenarioPath(String scenarioGroup, String code) {
+ return StringUtils.isNotEmpty(scenarioGroup) ? scenarioGroup + "/" + code : code;
+ }
+
+ @Override
+ public Set findByRelativeUrl(String projectCode, String relativeUrl) {
+ return findScenariosWithSteps(projectCode).stream()
+ .filter(scenario -> checkSteps(scenario, relativeUrl))
+ .collect(Collectors.toSet());
+ }
+
+ @Override
+ public void delete(String projectCode, String scenarioPath) throws IOException {
+ Path scenarioDirectory = Paths.get(projectsPath, projectCode, "scenarios", scenarioPath);
+ try (Stream filesStream = Files.walk(scenarioDirectory, FileVisitOption.FOLLOW_LINKS)) {
+ filesStream.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
+ }
+ }
+
+ private List findScenarios(String projectCode, boolean fetchSteps) {
+ File scenariosDirectory = Paths.get(projectsPath, projectCode, "scenarios").toFile();
+ if (!scenariosDirectory.exists()) {
+ return Collections.emptyList();
+ }
+ File[] directories = scenariosDirectory.listFiles(File::isDirectory);
+ if (directories == null) {
+ return Collections.emptyList();
+ }
+ List scenarios = new ArrayList<>();
+ for (File directory : directories) {
+ File scenarioYml = new File(directory, SCENARIO_YML_FILENAME);
+ if (scenarioYml.exists()) {
+ try {
+ scenarios.add(loadScenarioFromFiles(directory, null, fetchSteps));
+ } catch (IOException e) {
+ log.error("Read file " + scenarioYml.getAbsolutePath(), e);
+ }
+ } else {
+ File[] innerFileList = directory.listFiles(File::isDirectory);
+ if (innerFileList != null) {
+ for (File scenarioYmlInGroup : innerFileList) {
+ if (new File(scenarioYmlInGroup, SCENARIO_YML_FILENAME).exists()) {
+ try {
+ scenarios.add(loadScenarioFromFiles(scenarioYmlInGroup, directory.getName(), fetchSteps));
+ } catch (IOException | ReaderException e) {
+ log.error("Read file {} {}", scenarioYmlInGroup, e);
+ }
+ }
+ }
+ }
+ }
+ }
+ return scenarios;
+ }
+
+ private boolean checkSteps(Scenario scenario, String relativeUrl) {
+ return scenario.getStepList()
+ .stream()
+ .anyMatch(s -> containsIgnoreCase(s.getRelativeUrl(), relativeUrl));
+ }
+
+}
diff --git a/atf-application/src/main/java/ru/bsc/test/autotester/ro/ExpectedMqRequestRo.java b/atf-application/src/main/java/ru/bsc/test/autotester/ro/ExpectedMqRequestRo.java
index 29772a8b..2c4ea91e 100644
--- a/atf-application/src/main/java/ru/bsc/test/autotester/ro/ExpectedMqRequestRo.java
+++ b/atf-application/src/main/java/ru/bsc/test/autotester/ro/ExpectedMqRequestRo.java
@@ -23,10 +23,12 @@
import lombok.Getter;
import lombok.Setter;
+import java.io.Serializable;
+
@Getter
@Setter
@ApiModel(description = "Request to MQ stub that need to check")
-public class ExpectedMqRequestRo {
+public class ExpectedMqRequestRo implements Serializable {
@ApiModelProperty("Unique MQ request code")
private String code;
@ApiModelProperty("Name of service being tested queue, which define in sourceQueueName param of the properties.yml")
diff --git a/atf-application/src/main/java/ru/bsc/test/autotester/ro/HeaderItemRo.java b/atf-application/src/main/java/ru/bsc/test/autotester/ro/HeaderItemRo.java
index 222e36ba..3346faef 100644
--- a/atf-application/src/main/java/ru/bsc/test/autotester/ro/HeaderItemRo.java
+++ b/atf-application/src/main/java/ru/bsc/test/autotester/ro/HeaderItemRo.java
@@ -23,10 +23,12 @@
import lombok.Getter;
import lombok.Setter;
+import java.io.Serializable;
+
@Getter
@Setter
@ApiModel(description = "HTTP header")
-public class HeaderItemRo {
+public class HeaderItemRo implements Serializable {
@ApiModelProperty("Name of header")
String headerName;
@ApiModelProperty("Value of header")
diff --git a/atf-application/src/main/java/ru/bsc/test/autotester/ro/MqMockResponseRo.java b/atf-application/src/main/java/ru/bsc/test/autotester/ro/MqMockResponseRo.java
index b952094c..311e03f2 100644
--- a/atf-application/src/main/java/ru/bsc/test/autotester/ro/MqMockResponseRo.java
+++ b/atf-application/src/main/java/ru/bsc/test/autotester/ro/MqMockResponseRo.java
@@ -23,6 +23,8 @@
import lombok.Getter;
import lombok.Setter;
+import java.io.Serializable;
+
/**
* Created by smakarov
* 21.05.2018 15:39
@@ -30,7 +32,7 @@
@Getter
@Setter
@ApiModel(description = "Response from MQ stub")
-public class MqMockResponseRo {
+public class MqMockResponseRo implements Serializable {
@ApiModelProperty("Response message text")
private String responseBody;
@ApiModelProperty("Name of the destination queue")
diff --git a/atf-application/src/main/java/ru/bsc/test/autotester/ro/MqMockRo.java b/atf-application/src/main/java/ru/bsc/test/autotester/ro/MqMockRo.java
index aab47c1f..67928914 100644
--- a/atf-application/src/main/java/ru/bsc/test/autotester/ro/MqMockRo.java
+++ b/atf-application/src/main/java/ru/bsc/test/autotester/ro/MqMockRo.java
@@ -23,12 +23,13 @@
import lombok.Getter;
import lombok.Setter;
+import java.io.Serializable;
import java.util.List;
@Getter
@Setter
@ApiModel(description = "Responses which MQ stub returns")
-public class MqMockRo {
+public class MqMockRo implements Serializable {
@ApiModelProperty("Unique response code")
private String code;
@ApiModelProperty("Name of service being tested queue, which define in sourceQueueName param of the properties.yml")
diff --git a/atf-application/src/main/java/ru/bsc/test/autotester/ro/NameValuePropertyRo.java b/atf-application/src/main/java/ru/bsc/test/autotester/ro/NameValuePropertyRo.java
index 0a069dac..4c81c688 100644
--- a/atf-application/src/main/java/ru/bsc/test/autotester/ro/NameValuePropertyRo.java
+++ b/atf-application/src/main/java/ru/bsc/test/autotester/ro/NameValuePropertyRo.java
@@ -23,10 +23,12 @@
import lombok.Getter;
import lombok.Setter;
+import java.io.Serializable;
+
@Getter
@Setter
@ApiModel(description = "Property which is send with message body to queue")
-public class NameValuePropertyRo {
+public class NameValuePropertyRo implements Serializable {
@ApiModelProperty("Name of property")
private String name;
@ApiModelProperty("value of property")
diff --git a/atf-application/src/main/java/ru/bsc/test/autotester/ro/RequestDataRo.java b/atf-application/src/main/java/ru/bsc/test/autotester/ro/RequestDataRo.java
index abedc999..e61aafb1 100644
--- a/atf-application/src/main/java/ru/bsc/test/autotester/ro/RequestDataRo.java
+++ b/atf-application/src/main/java/ru/bsc/test/autotester/ro/RequestDataRo.java
@@ -23,10 +23,12 @@
import lombok.Getter;
import lombok.Setter;
+import java.io.Serializable;
+
@Getter
@Setter
@ApiModel(description = "Actual request made while step executing")
-public class RequestDataRo {
+public class RequestDataRo implements Serializable {
@ApiModelProperty("Actual request body text")
private String requestBody;
@ApiModelProperty("Actual response body text")
diff --git a/atf-application/src/main/java/ru/bsc/test/autotester/ro/ScenarioVariableFromMqRequestRo.java b/atf-application/src/main/java/ru/bsc/test/autotester/ro/ScenarioVariableFromMqRequestRo.java
index 7ed49a9a..cd165c42 100644
--- a/atf-application/src/main/java/ru/bsc/test/autotester/ro/ScenarioVariableFromMqRequestRo.java
+++ b/atf-application/src/main/java/ru/bsc/test/autotester/ro/ScenarioVariableFromMqRequestRo.java
@@ -22,9 +22,11 @@
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
+import java.io.Serializable;
+
@Data
@ApiModel(description = "Scenario variable definition which need to save from request to MQ stub")
-public class ScenarioVariableFromMqRequestRo {
+public class ScenarioVariableFromMqRequestRo implements Serializable {
@ApiModelProperty("Name of service being tested queue, which define in sourceQueueName param of the properties.yml")
private String sourceQueue;
@ApiModelProperty("XPath according to which needs to save variable value")
diff --git a/atf-application/src/main/java/ru/bsc/test/autotester/ro/ScenarioVariableFromServiceRequestRo.java b/atf-application/src/main/java/ru/bsc/test/autotester/ro/ScenarioVariableFromServiceRequestRo.java
index 962b79f2..9e86cfc2 100644
--- a/atf-application/src/main/java/ru/bsc/test/autotester/ro/ScenarioVariableFromServiceRequestRo.java
+++ b/atf-application/src/main/java/ru/bsc/test/autotester/ro/ScenarioVariableFromServiceRequestRo.java
@@ -20,8 +20,10 @@
import lombok.Data;
+import java.io.Serializable;
+
@Data
-public class ScenarioVariableFromServiceRequestRo {
+public class ScenarioVariableFromServiceRequestRo implements Serializable {
private String scenarioVariableName;
private String expression;
private String matchingType;
diff --git a/atf-application/src/main/java/ru/bsc/test/autotester/ro/StepResultRo.java b/atf-application/src/main/java/ru/bsc/test/autotester/ro/StepResultRo.java
index f037d2fb..a876f641 100644
--- a/atf-application/src/main/java/ru/bsc/test/autotester/ro/StepResultRo.java
+++ b/atf-application/src/main/java/ru/bsc/test/autotester/ro/StepResultRo.java
@@ -24,6 +24,7 @@
import lombok.Setter;
import ru.bsc.test.autotester.diff.Diff;
+import java.io.Serializable;
import java.util.List;
/**
@@ -32,7 +33,7 @@
@Getter
@Setter
@ApiModel(description = "Result of step execution")
-public class StepResultRo {
+public class StepResultRo implements Serializable {
@ApiModelProperty("Random testId")
private String testId;
@ApiModelProperty("Step which has been executed")
diff --git a/atf-commons/pom.xml b/atf-commons/pom.xml
index 3d8a67db..19bec878 100644
--- a/atf-commons/pom.xml
+++ b/atf-commons/pom.xml
@@ -98,6 +98,53 @@
1.8
+
+
+ org.codehaus.mojo
+ findbugs-maven-plugin
+ 3.0.5
+
+ Max
+
+ medium
+
+ true
+ FindReturnRef,RuntimeExceptionCapture
+
+
+
+ analyze-compile
+ compile
+
+ check
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/atf-commons/src/main/java/ru/bsc/test/at/util/YamlUtils.java b/atf-commons/src/main/java/ru/bsc/test/at/util/YamlUtils.java
index d643d8ca..317c95c3 100644
--- a/atf-commons/src/main/java/ru/bsc/test/at/util/YamlUtils.java
+++ b/atf-commons/src/main/java/ru/bsc/test/at/util/YamlUtils.java
@@ -61,8 +61,9 @@ public static boolean saveToFile(T data, Path path) {
public static T loadAs(File fileName, Class type) throws IOException {
Representer representer = new Representer();
representer.getPropertyUtils().setSkipMissingProperties(true);
- try (FileReader fileReader = new FileReader(fileName)) {
- return new Yaml(representer).loadAs(fileReader, type);
+ try (FileInputStream fileStream = new FileInputStream(fileName);
+ InputStreamReader reader = new InputStreamReader(fileStream, StandardCharsets.UTF_8)) {
+ return new Yaml(representer).loadAs(reader, type);
}
}
diff --git a/atf-executor/pom.xml b/atf-executor/pom.xml
index 52f4fc9a..38c7fc58 100644
--- a/atf-executor/pom.xml
+++ b/atf-executor/pom.xml
@@ -189,6 +189,53 @@
+
+
+ org.codehaus.mojo
+ findbugs-maven-plugin
+ 3.0.5
+
+ Max
+
+ medium
+
+ true
+ FindReturnRef,RuntimeExceptionCapture
+
+
+
+ analyze-compile
+ compile
+
+ check
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/exception/ComparisonException.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/exception/ComparisonException.java
index 362c3418..4476196b 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/exception/ComparisonException.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/exception/ComparisonException.java
@@ -1,45 +1,45 @@
-/*
- * Copyright 2018 BSC Msc, LLC
- *
- * This file is part of the AuTe Framework project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ru.bsc.test.at.executor.exception;
-
-import org.xmlunit.diff.Diff;
-
-/**
- * Created by smakarov
- * 20.02.2018 10:52
- */
-public class ComparisonException extends RuntimeException {
- public ComparisonException(Diff diff, String expectedRequest, String actualRequest) {
- super(String.format(
- "Service request error (request differences):%s\n\tExpected: %s\n\tActual: %s\n",
- diff,
- expectedRequest,
- actualRequest
- ));
- }
-
- public ComparisonException(String diff, String expectedRequest, String actualRequest) {
- super(String.format(
- "Service request error (request differences):%s\n\tExpected: %s\n\tActual: %s\n",
- diff,
- expectedRequest,
- actualRequest
- ));
- }
-}
+/*
+ * Copyright 2018 BSC Msc, LLC
+ *
+ * This file is part of the AuTe Framework project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ru.bsc.test.at.executor.exception;
+
+import org.xmlunit.diff.Diff;
+
+/**
+ * Created by smakarov
+ * 20.02.2018 10:52
+ */
+public class ComparisonException extends RuntimeException {
+ public ComparisonException(Diff diff, String expectedRequest, String actualRequest) {
+ super(String.format(
+ "Service request error (request differences):%s%n\tExpected: %s%n\tActual: %s%n",
+ diff,
+ expectedRequest,
+ actualRequest
+ ));
+ }
+
+ public ComparisonException(String diff, String expectedRequest, String actualRequest) {
+ super(String.format(
+ "Service request error (request differences):%s%n\tExpected: %s%n\tActual: %s%n",
+ diff,
+ expectedRequest,
+ actualRequest
+ ));
+ }
+}
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/exception/InvalidNumberOfMockRequests.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/exception/InvalidNumberOfMockRequests.java
index d524faa1..1f108989 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/exception/InvalidNumberOfMockRequests.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/exception/InvalidNumberOfMockRequests.java
@@ -16,8 +16,6 @@
package ru.bsc.test.at.executor.exception;
-import java.util.List;
-import java.util.stream.Collectors;
/**
* Created by smakarov
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/helper/IgnoreTagsDifferenceEvaluator.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/helper/IgnoreTagsDifferenceEvaluator.java
index 1ec896a5..448dd6c8 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/helper/IgnoreTagsDifferenceEvaluator.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/helper/IgnoreTagsDifferenceEvaluator.java
@@ -1,197 +1,195 @@
-/*
- * Copyright 2018 BSC Msc, LLC
- *
- * This file is part of the AuTe Framework project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ru.bsc.test.at.executor.helper;
-
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.xmlunit.diff.Comparison;
-import org.xmlunit.diff.ComparisonResult;
-import org.xmlunit.diff.ComparisonType;
-import org.xmlunit.diff.DifferenceEvaluator;
-import ru.bsc.test.at.executor.validation.MaskComparator;
-
-import java.util.*;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static org.xmlunit.diff.ComparisonType.*;
-
-/**
- * Created by rmalyshev date: 30.11.12
- *
- */
-class IgnoreTagsDifferenceEvaluator implements DifferenceEvaluator {
-
- private static final String TAG_REGEX = "\\s*(\\w+)\\s*\\(\\s*(\\w+)\\s*=\\s*([\\w#]+)\\s*\\)";
- private static final Set COMPARISON_TYPES = Collections.unmodifiableSet(EnumSet.of(
- NAMESPACE_PREFIX,
- NAMESPACE_URI,
- NO_NAMESPACE_SCHEMA_LOCATION,
- SCHEMA_LOCATION
- ));
-
- private Set ignoredTags = new HashSet<>();
-
- IgnoreTagsDifferenceEvaluator(Set ignoredTags) {
- if (ignoredTags != null) {
- this.ignoredTags = ignoredTags;
- }
- }
-
- private boolean isXSIType(Node node) {
- return node.getNodeType() == Node.ATTRIBUTE_NODE &&
- node.getLocalName().compareTo("type") == 0 &&
- Objects.equals(node.getNamespaceURI(), "http://www.w3.org/2001/XMLSchema-instance");
- }
-
- private String getNameSpaceFromPrefix(Node node) {
- final int beginIndex = node.getNodeValue().indexOf(':');
- if (beginIndex == -1) {
- return "";
- }
- return node.lookupNamespaceURI(node.getNodeValue().substring(0, beginIndex));
- }
-
- private String getNameWithoutPrefix(Node controlNode) {
- final int beginIndex = controlNode.getNodeValue().indexOf(':');
- if (beginIndex == -1) {
- return controlNode.getNodeValue();
- }
- return controlNode.getNodeValue().substring(beginIndex);
- }
-
- @Override
- public ComparisonResult evaluate(Comparison comparison, ComparisonResult outcome) {
- if (outcome == ComparisonResult.EQUAL) {
- return outcome;
- }
- if (outcome == ComparisonResult.DIFFERENT && checkToDifferent(comparison)) {
- return ComparisonResult.EQUAL;
- }
-
- final Node controlNode = comparison.getControlDetails().getTarget();
- final Node testNode = comparison.getTestDetails().getTarget();
-
- if (comparison.getType() == ComparisonType.ATTR_VALUE && isXSIType(controlNode) && isXSIType(testNode)) {
- if (getNameSpaceFromPrefix(controlNode).compareTo(getNameSpaceFromPrefix(testNode)) != 0) {
- return ComparisonResult.DIFFERENT;
- }
-
- String withoutPrefixControl = getNameWithoutPrefix(controlNode);
- String withoutPrefixTest = getNameWithoutPrefix(testNode);
- if (withoutPrefixControl.compareTo(withoutPrefixTest) == 0) {
- return ComparisonResult.EQUAL;
- }
- }
-
- if(controlNode == null || ignoredTags == null) {
- return outcome;
- }
-
- if (checkControlNode(controlNode)) {
- return ComparisonResult.EQUAL;
- }
-
- // *ignore* check
- if (testNode != null && MaskComparator.compare(controlNode.getTextContent(), testNode.getTextContent())) {
- return ComparisonResult.EQUAL;
- }
-
- return outcome;
- }
-
- private boolean checkControlNode(Node controlNode) {
- Pattern pattern = Pattern.compile(TAG_REGEX);
- for (String ignoredTag : this.ignoredTags) {
- if (isControlNodeTag(ignoredTag, controlNode)) {
- return true;
- }
- Matcher matcher = pattern.matcher(ignoredTag);
- if(!matcher.find()) {
- continue;
- }
-
- String parent = matcher.group(1);
- String childKey = matcher.group(2);
- String childValue = matcher.group(3);
- NodeList childNodes = getNodeList(controlNode, parent, childKey);
- if (childNodes == null) {
- continue;
- }
- for (int i = 0; i < childNodes.getLength(); i++) {
- if (!(childNodes.item(i) instanceof Element) ||
- !((Element) childNodes.item(i)).getTagName().equals(childKey)) {
- continue;
- }
-
- Element childElement = (Element) childNodes.item(i);
- if (childElement.getTagName().equals(childKey) &&
- childElement.getTextContent().equals(childValue)) {
- return true;
- }
- }
- }
- return false;
- }
-
- private NodeList getNodeList(Node controlNode, String parent, String childKey) {
- if(!(controlNode.getParentNode() instanceof Element)) {
- return null;
- }
-
- Element controlElement = (Element) controlNode.getParentNode();
- if(!(controlElement.getParentNode() instanceof Element)) {
- return null;
- }
-
- Element parentElement = (Element) controlElement.getParentNode();
- if(!parentElement.getTagName().equals(parent) || !parentElement.hasChildNodes()) {
- return null;
- }
-
- NodeList childNodes = parentElement.getElementsByTagName(childKey);
- if(childNodes.getLength() <= 0) {
- return null;
- }
- return childNodes;
- }
-
- private boolean isControlNodeTag(String ignoredTag, Node controlNode) {
- if (!ignoredTag.matches(TAG_REGEX)) {
- if (controlNode.getParentNode() instanceof Element) {
- Element element = (Element) controlNode.getParentNode();
- return ignoredTag.equals(element.getTagName());
- }
- }
- return false;
- }
-
- private boolean checkToDifferent(Comparison comparison) {
- ComparisonType comparisonType = comparison.getType();
- if (ATTR_VALUE.equals(comparisonType) || ATTR_NAME_LOOKUP.equals(comparisonType)) {
- Attr target = (Attr) comparison.getControlDetails().getTarget();
- String parentNodeName = target.getOwnerElement().getLocalName();
- return ignoredTags.contains(parentNodeName);
- }
-
- return COMPARISON_TYPES.contains(comparisonType);
- }
-}
+/*
+ * Copyright 2018 BSC Msc, LLC
+ *
+ * This file is part of the AuTe Framework project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ru.bsc.test.at.executor.helper;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xmlunit.diff.Comparison;
+import org.xmlunit.diff.ComparisonResult;
+import org.xmlunit.diff.ComparisonType;
+import org.xmlunit.diff.DifferenceEvaluator;
+import ru.bsc.test.at.executor.validation.MaskComparator;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.xmlunit.diff.ComparisonType.*;
+
+/**
+ * Created by rmalyshev date: 30.11.12
+ *
+ */
+class IgnoreTagsDifferenceEvaluator implements DifferenceEvaluator {
+
+ private static final String TAG_REGEX = "\\s*(\\w+)\\s*\\(\\s*(\\w+)\\s*=\\s*([\\w#]+)\\s*\\)";
+ private static final Set COMPARISON_TYPES = Collections.unmodifiableSet(EnumSet.of(
+ NAMESPACE_PREFIX,
+ NAMESPACE_URI,
+ NO_NAMESPACE_SCHEMA_LOCATION,
+ SCHEMA_LOCATION
+ ));
+
+ private Set ignoredTags = new HashSet<>();
+
+ IgnoreTagsDifferenceEvaluator(Set ignoredTags) {
+ if (ignoredTags != null) {
+ this.ignoredTags = ignoredTags;
+ }
+ }
+
+ private boolean isXSIType(Node node) {
+ return node.getNodeType() == Node.ATTRIBUTE_NODE &&
+ node.getLocalName().compareTo("type") == 0 &&
+ Objects.equals(node.getNamespaceURI(), "http://www.w3.org/2001/XMLSchema-instance");
+ }
+
+ private String getNameSpaceFromPrefix(Node node) {
+ final int beginIndex = node.getNodeValue().indexOf(':');
+ if (beginIndex == -1) {
+ return "";
+ }
+ return node.lookupNamespaceURI(node.getNodeValue().substring(0, beginIndex));
+ }
+
+ private String getNameWithoutPrefix(Node controlNode) {
+ final int beginIndex = controlNode.getNodeValue().indexOf(':');
+ if (beginIndex == -1) {
+ return controlNode.getNodeValue();
+ }
+ return controlNode.getNodeValue().substring(beginIndex);
+ }
+
+ @Override
+ public ComparisonResult evaluate(Comparison comparison, ComparisonResult outcome) {
+ if (outcome == ComparisonResult.EQUAL) {
+ return outcome;
+ }
+ if (outcome == ComparisonResult.DIFFERENT && checkToDifferent(comparison)) {
+ return ComparisonResult.EQUAL;
+ }
+
+ final Node controlNode = comparison.getControlDetails().getTarget();
+ final Node testNode = comparison.getTestDetails().getTarget();
+
+ if (comparison.getType() == ATTR_VALUE && isXSIType(controlNode) && isXSIType(testNode)) {
+ if (getNameSpaceFromPrefix(controlNode).compareTo(getNameSpaceFromPrefix(testNode)) != 0) {
+ return ComparisonResult.DIFFERENT;
+ }
+
+ String withoutPrefixControl = getNameWithoutPrefix(controlNode);
+ String withoutPrefixTest = getNameWithoutPrefix(testNode);
+ if (withoutPrefixControl.compareTo(withoutPrefixTest) == 0) {
+ return ComparisonResult.EQUAL;
+ }
+ }
+
+ if(controlNode == null || ignoredTags == null) {
+ return outcome;
+ }
+
+ if (checkControlNode(controlNode)) {
+ return ComparisonResult.EQUAL;
+ }
+
+ // *ignore* check
+ if (testNode != null && MaskComparator.compare(controlNode.getTextContent(), testNode.getTextContent())) {
+ return ComparisonResult.EQUAL;
+ }
+
+ return outcome;
+ }
+
+ private boolean checkControlNode(Node controlNode) {
+ Pattern pattern = Pattern.compile(TAG_REGEX);
+ for (String ignoredTag : this.ignoredTags) {
+ if (isControlNodeTag(ignoredTag, controlNode)) {
+ return true;
+ }
+ Matcher matcher = pattern.matcher(ignoredTag);
+ if(!matcher.find()) {
+ continue;
+ }
+
+ String parent = matcher.group(1);
+ String childKey = matcher.group(2);
+ String childValue = matcher.group(3);
+ NodeList childNodes = getNodeList(controlNode, parent, childKey);
+ if (childNodes == null) {
+ continue;
+ }
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ if (!(childNodes.item(i) instanceof Element) ||
+ !((Element) childNodes.item(i)).getTagName().equals(childKey)) {
+ continue;
+ }
+
+ Element childElement = (Element) childNodes.item(i);
+ if (childElement.getTagName().equals(childKey) &&
+ childElement.getTextContent().equals(childValue)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private NodeList getNodeList(Node controlNode, String parent, String childKey) {
+ if(!(controlNode.getParentNode() instanceof Element)) {
+ return null;
+ }
+
+ Element controlElement = (Element) controlNode.getParentNode();
+ if(!(controlElement.getParentNode() instanceof Element)) {
+ return null;
+ }
+
+ Element parentElement = (Element) controlElement.getParentNode();
+ if(!parentElement.getTagName().equals(parent) || !parentElement.hasChildNodes()) {
+ return null;
+ }
+
+ NodeList childNodes = parentElement.getElementsByTagName(childKey);
+ if(childNodes.getLength() <= 0) {
+ return null;
+ }
+ return childNodes;
+ }
+
+ private boolean isControlNodeTag(String ignoredTag, Node controlNode) {
+ if (!ignoredTag.matches(TAG_REGEX) && controlNode.getParentNode() instanceof Element) {
+ Element element = (Element) controlNode.getParentNode();
+ return ignoredTag.equals(element.getTagName());
+ }
+ return false;
+ }
+
+ private boolean checkToDifferent(Comparison comparison) {
+ ComparisonType comparisonType = comparison.getType();
+ if (ATTR_VALUE.equals(comparisonType) || ATTR_NAME_LOOKUP.equals(comparisonType)) {
+ Attr target = (Attr) comparison.getControlDetails().getTarget();
+ String parentNodeName = target.getOwnerElement().getLocalName();
+ return ignoredTags.contains(parentNodeName);
+ }
+
+ return COMPARISON_TYPES.contains(comparisonType);
+ }
+}
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/helper/MqMockHelper.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/helper/MqMockHelper.java
index d66961b5..d4bf6a4d 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/helper/MqMockHelper.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/helper/MqMockHelper.java
@@ -82,6 +82,7 @@ public void assertMqRequests(WireMockAdmin mqMockerAdmin, String testId, Step st
}catch (JsonParseException ex) {
// DO NOTING (не во всех wiremock есть mq)
+ log.error("Error parsing: ", ex.getMessage());
}
return;
}
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/helper/client/impl/http/ClientHttpRequest.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/helper/client/impl/http/ClientHttpRequest.java
index e6469cc5..e746ab18 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/helper/client/impl/http/ClientHttpRequest.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/helper/client/impl/http/ClientHttpRequest.java
@@ -26,43 +26,60 @@
/**
* @author Pavel Golovkin
*/
-@AllArgsConstructor
public class ClientHttpRequest implements ClientRequest {
- protected final String url;
- protected final Object body;
- protected final HTTPMethod method;
- protected final Map headers;
- protected final String testId;
- protected final String testIdHeaderName;
+ protected final String url;
+ protected final Object body;
+ protected final HTTPMethod method;
+ protected final Map headers;
+ protected final String testId;
+ protected final String testIdHeaderName;
+ protected final boolean useResponseAsBase64;
- @Override
- public String getResource() {
- return url;
+ public ClientHttpRequest(String url, Object body, HTTPMethod method, Map headers, String testId, String testIdHeaderName) {
+ this(url, body, method, headers, testId, testIdHeaderName, false);
}
- @Override
- public Object getBody() {
- return body;
+ public ClientHttpRequest(String url, Object body, HTTPMethod method, Map headers, String testId, String testIdHeaderName, boolean useResponseAsBase64) {
+ this.url = url;
+ this.body = body;
+ this.method = method;
+ this.headers = headers;
+ this.testId = testId;
+ this.testIdHeaderName = testIdHeaderName;
+ this.useResponseAsBase64 = useResponseAsBase64;
}
@Override
- public Map getHeaders() {
- return headers;
- }
+ public String getResource() {
+ return url;
+ }
- @Override
- public String getTestId() {
- return testId;
- }
+ @Override
+ public Object getBody() {
+ return body;
+ }
- @Override
- public String getTestIdHeaderName() {
- return testIdHeaderName;
- }
+ @Override
+ public Map getHeaders() {
+ return headers;
+ }
- public HTTPMethod getMethod() {
- return method;
- }
+ @Override
+ public String getTestId() {
+ return testId;
+ }
+
+ @Override
+ public String getTestIdHeaderName() {
+ return testIdHeaderName;
+ }
+
+ public boolean getUseResponseAsBase64() {
+ return useResponseAsBase64;
+ }
+ public HTTPMethod getMethod() {
+ return method;
+ }
}
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/helper/client/impl/http/HttpClient.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/helper/client/impl/http/HttpClient.java
index fd4cdef3..e17a1705 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/helper/client/impl/http/HttpClient.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/helper/client/impl/http/HttpClient.java
@@ -50,6 +50,7 @@
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.*;
+import java.util.Map.Entry;
import java.util.stream.Collectors;
/**
@@ -74,10 +75,10 @@ public List getCookies() {
public ClientCommonResponse request(ClientHttpRequest request) throws Exception {
if (request instanceof ClientHttpRequestWithVariables) {
return executeWithScenarioVariables((ClientHttpRequestWithVariables) request);
- } else if (request instanceof ClientHttpRequest) {
+ } else if (request != null) { // every request is ClientHttpRequest!
return executeWithoutScenarioVariables(request);
} else {
- throw new Exception("Unsupported request " + request.getClass());
+ throw new Exception("Unsupported request");
}
}
@@ -163,12 +164,10 @@ private void setHeaders(HttpRequestBase request, Map headers) {
if (headers == null || headers.isEmpty()) {
return;
}
- for (Object key: headers.keySet()) {
- if (key != null) {
- String keyStr = (String) key;
- if (StringUtils.isNotEmpty(keyStr)) {
- request.addHeader(keyStr, (String) headers.get(key));
- }
+ for (Object entryObj : headers.entrySet()) {
+ Entry entry = (Entry)entryObj;
+ if (StringUtils.isNotEmpty(entry.getKey())) {
+ request.addHeader(entry.getKey(), entry.getValue());
}
}
}
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/helper/client/impl/mq/MqClient.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/helper/client/impl/mq/MqClient.java
index c3bc5027..befa1ece 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/helper/client/impl/mq/MqClient.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/helper/client/impl/mq/MqClient.java
@@ -30,6 +30,7 @@
import javax.jms.Message;
import javax.jms.TextMessage;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
@@ -91,7 +92,7 @@ public ClientCommonResponse waitMessage(String queueName, Long timeout, String t
BytesMessage bytesMessage = (BytesMessage) message;
byte[] data = new byte[(int) bytesMessage.getBodyLength()];
bytesMessage.readBytes(data);
- return new ClientCommonResponse(0, new String(data), null);
+ return new ClientCommonResponse(0, new String(data, StandardCharsets.UTF_8), null);
} else if (message instanceof TextMessage) {
return new ClientCommonResponse(0, ((TextMessage) message).getText(), null);
} else {
@@ -109,7 +110,7 @@ public void close() throws IOException {
}
}
- class ClientVoidResponse implements ClientResponse {
+ static class ClientVoidResponse implements ClientResponse {
@Override
public int getStatusCode() {
return 0;
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/model/AbstractModel.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/model/AbstractModel.java
index 7224d814..d4e3c123 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/model/AbstractModel.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/model/AbstractModel.java
@@ -1,26 +1,28 @@
-/*
- * Copyright 2018 BSC Msc, LLC
- *
- * This file is part of the AuTe Framework project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ru.bsc.test.at.executor.model;
-
-/**
- * Created by sdoroshin on 31.10.2017.
- *
- */
-public interface AbstractModel {
-}
+/*
+ * Copyright 2018 BSC Msc, LLC
+ *
+ * This file is part of the AuTe Framework project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ru.bsc.test.at.executor.model;
+
+import java.io.Serializable;
+
+/**
+ * Created by sdoroshin on 31.10.2017.
+ *
+ */
+public interface AbstractModel extends Serializable {
+}
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/model/CodeAccessible.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/model/CodeAccessible.java
index 2220f9cd..c5f04149 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/model/CodeAccessible.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/model/CodeAccessible.java
@@ -1,35 +1,36 @@
-/*
- * Copyright 2018 BSC Msc, LLC
- *
- * This file is part of the AuTe Framework project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ru.bsc.test.at.executor.model;
-
-import java.util.UUID;
-
-/**
- * Created by smakarov
- * 20.03.2018 10:38
- */
-public interface CodeAccessible {
- String getCode();
-
- void setCode(String code);
-
- default void generateCode() {
- setCode(UUID.randomUUID().toString());
- }
-}
+/*
+ * Copyright 2018 BSC Msc, LLC
+ *
+ * This file is part of the AuTe Framework project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ru.bsc.test.at.executor.model;
+
+import java.io.Serializable;
+import java.util.UUID;
+
+/**
+ * Created by smakarov
+ * 20.03.2018 10:38
+ */
+public interface CodeAccessible extends Serializable {
+ String getCode();
+
+ void setCode(String code);
+
+ default void generateCode() {
+ setCode(UUID.randomUUID().toString());
+ }
+}
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/model/HeaderItem.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/model/HeaderItem.java
index a833d8ea..c2495f3c 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/model/HeaderItem.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/model/HeaderItem.java
@@ -1,38 +1,40 @@
-/*
- * Copyright 2018 BSC Msc, LLC
- *
- * This file is part of the AuTe Framework project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ru.bsc.test.at.executor.model;
-
-import lombok.Getter;
-import lombok.Setter;
-
-@Getter
-@Setter
-public class HeaderItem {
- String headerName;
- String headerValue;
- String compareType;
-
- public HeaderItem copy() {
- HeaderItem copy = new HeaderItem();
- copy.setCompareType(getCompareType());
- copy.setHeaderValue(getHeaderValue());
- copy.setHeaderName(getHeaderName());
- return copy;
- }
-}
+/*
+ * Copyright 2018 BSC Msc, LLC
+ *
+ * This file is part of the AuTe Framework project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ru.bsc.test.at.executor.model;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+@Getter
+@Setter
+public class HeaderItem implements Serializable {
+ String headerName;
+ String headerValue;
+ String compareType;
+
+ public HeaderItem copy() {
+ HeaderItem copy = new HeaderItem();
+ copy.setCompareType(getCompareType());
+ copy.setHeaderValue(getHeaderValue());
+ copy.setHeaderName(getHeaderName());
+ return copy;
+ }
+}
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/model/MqMessage.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/model/MqMessage.java
index efa2bfa8..1388c9f2 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/model/MqMessage.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/model/MqMessage.java
@@ -1,59 +1,58 @@
-/*
- * Copyright 2018 BSC Msc, LLC
- *
- * This file is part of the AuTe Framework project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ru.bsc.test.at.executor.model;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import lombok.Getter;
-import lombok.Setter;
-import org.apache.commons.lang3.StringUtils;
-
-import java.io.Serializable;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Created by smakarov
- * 20.04.2018 12:10
- */
-@Getter
-@Setter
-public class MqMessage implements Serializable, AbstractModel {
- private static final long serialVersionUID = -2284237307005166339L;
-
- private String queueName;
- private String message;
- private String messageFile;
- private List properties = new LinkedList<>();
-
- public MqMessage copy() {
- MqMessage copy = new MqMessage();
- copy.setQueueName(getQueueName());
- copy.setMessage(getMessage());
- if (getProperties() != null) {
- copy.setProperties(new LinkedList<>());
- properties.forEach(p -> copy.getProperties().add(p.copy()));
- }
- return copy;
- }
-
- @JsonIgnore
- public boolean isEmpty() {
- return StringUtils.isEmpty(this.queueName) || StringUtils.isEmpty(this.message);
- }
-}
+/*
+ * Copyright 2018 BSC Msc, LLC
+ *
+ * This file is part of the AuTe Framework project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ru.bsc.test.at.executor.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Created by smakarov
+ * 20.04.2018 12:10
+ */
+@Getter
+@Setter
+public class MqMessage implements AbstractModel {
+ private static final long serialVersionUID = -2284237307005166339L;
+
+ private String queueName;
+ private String message;
+ private String messageFile;
+ private List properties = new LinkedList<>();
+
+ public MqMessage copy() {
+ MqMessage copy = new MqMessage();
+ copy.setQueueName(getQueueName());
+ copy.setMessage(getMessage());
+ if (getProperties() != null) {
+ copy.setProperties(new LinkedList<>());
+ properties.forEach(p -> copy.getProperties().add(p.copy()));
+ }
+ return copy;
+ }
+
+ @JsonIgnore
+ public boolean isEmpty() {
+ return StringUtils.isEmpty(this.queueName) || StringUtils.isEmpty(this.message);
+ }
+}
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/model/MqMockResponse.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/model/MqMockResponse.java
index b3bdde2c..a9f8008f 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/model/MqMockResponse.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/model/MqMockResponse.java
@@ -1,41 +1,43 @@
-/*
- * Copyright 2018 BSC Msc, LLC
- *
- * This file is part of the AuTe Framework project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ru.bsc.test.at.executor.model;
-
-import lombok.Getter;
-import lombok.Setter;
-
-/**
- * Created by smakarov
- * 21.05.2018 15:34
- */
-@Getter
-@Setter
-public class MqMockResponse {
- private String responseBody;
- private String destinationQueueName;
- private String responseFile;
-
- public MqMockResponse copy() {
- MqMockResponse copy = new MqMockResponse();
- copy.setDestinationQueueName(getDestinationQueueName());
- copy.setResponseBody(getResponseBody());
- return copy;
- }
-}
+/*
+ * Copyright 2018 BSC Msc, LLC
+ *
+ * This file is part of the AuTe Framework project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ru.bsc.test.at.executor.model;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+/**
+ * Created by smakarov
+ * 21.05.2018 15:34
+ */
+@Getter
+@Setter
+public class MqMockResponse implements Serializable {
+ private String responseBody;
+ private String destinationQueueName;
+ private String responseFile;
+
+ public MqMockResponse copy() {
+ MqMockResponse copy = new MqMockResponse();
+ copy.setDestinationQueueName(getDestinationQueueName());
+ copy.setResponseBody(getResponseBody());
+ return copy;
+ }
+}
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/model/NameValueProperty.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/model/NameValueProperty.java
index d027f6fc..2447494c 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/model/NameValueProperty.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/model/NameValueProperty.java
@@ -1,36 +1,38 @@
-/*
- * Copyright 2018 BSC Msc, LLC
- *
- * This file is part of the AuTe Framework project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ru.bsc.test.at.executor.model;
-
-import lombok.Getter;
-import lombok.Setter;
-
-@Getter
-@Setter
-public class NameValueProperty {
- private String name;
- private String value;
-
- public NameValueProperty copy() {
- NameValueProperty property = new NameValueProperty();
- property.setName(getName());
- property.setValue(getValue());
- return property;
- }
-}
+/*
+ * Copyright 2018 BSC Msc, LLC
+ *
+ * This file is part of the AuTe Framework project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ru.bsc.test.at.executor.model;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+@Getter
+@Setter
+public class NameValueProperty implements Serializable{
+ private String name;
+ private String value;
+
+ public NameValueProperty copy() {
+ NameValueProperty property = new NameValueProperty();
+ property.setName(getName());
+ property.setValue(getValue());
+ return property;
+ }
+}
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/model/SqlData.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/model/SqlData.java
index a1113eea..c82a4120 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/model/SqlData.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/model/SqlData.java
@@ -1,40 +1,38 @@
-/*
- * Copyright 2018 BSC Msc, LLC
- *
- * This file is part of the AuTe Framework project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ru.bsc.test.at.executor.model;
-
-import lombok.Data;
-
-import java.io.Serializable;
-
-@Data
-public class SqlData implements Serializable, AbstractModel {
- private static final long serialVersionUID = -5297373310164570345L;
-
- private String sql;
- private String sqlSavedParameter;
- private SqlResultType sqlReturnType = SqlResultType.MAP;
-
- public SqlData copy() {
- SqlData sqlData = new SqlData();
- sqlData.setSql(getSql());
- sqlData.setSqlSavedParameter(getSqlSavedParameter());
- sqlData.setSqlReturnType(getSqlReturnType());
- return sqlData;
- }
-}
+/*
+ * Copyright 2018 BSC Msc, LLC
+ *
+ * This file is part of the AuTe Framework project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ru.bsc.test.at.executor.model;
+
+import lombok.Data;
+
+@Data
+public class SqlData implements AbstractModel {
+ private static final long serialVersionUID = -5297373310164570345L;
+
+ private String sql;
+ private String sqlSavedParameter;
+ private SqlResultType sqlReturnType = SqlResultType.MAP;
+
+ public SqlData copy() {
+ SqlData sqlData = new SqlData();
+ sqlData.setSql(getSql());
+ sqlData.setSqlSavedParameter(getSqlSavedParameter());
+ sqlData.setSqlReturnType(getSqlReturnType());
+ return sqlData;
+ }
+}
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/mq/RabbitMqManager.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/mq/RabbitMqManager.java
index 45f82251..618c3679 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/mq/RabbitMqManager.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/mq/RabbitMqManager.java
@@ -1,129 +1,130 @@
-/*
- * Copyright 2018 BSC Msc, LLC
- *
- * This file is part of the AuTe Framework project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ru.bsc.test.at.executor.mq;
-
-import com.rabbitmq.client.AMQP;
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.ConnectionFactory;
-import com.rabbitmq.client.QueueingConsumer;
-import com.rabbitmq.jms.client.message.RMQTextMessage;
-import lombok.extern.slf4j.Slf4j;
-
-import javax.jms.Connection;
-import javax.jms.JMSException;
-import javax.jms.Message;
-import java.io.IOException;
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.HashMap;
-import java.util.Map;
-
-// Это реализация для работы с RabbitMQ.
-// Сообщения не отправляются в очередь через стандартные интерфейсы JMS, если у очереди указан параметр x-dead-letter-exchange
-
-@Slf4j
-class RabbitMqManager extends AbstractMqManager {
-
- private final com.rabbitmq.client.Connection senderConnection;
-
- RabbitMqManager(String host, int port, String username, String password) throws JMSException {
-
- try {
- ConnectionFactory connectionFactory = new ConnectionFactory();
- connectionFactory.setHost(host);
- connectionFactory.setPort(port);
- connectionFactory.setUsername(username);
- connectionFactory.setPassword(password);
- senderConnection = connectionFactory.newConnection();
- } catch (Exception e) {
- log.error("{}", e);
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public void sendTextMessage(String queueName, String message, Map properties, String testIdHeaderName, String testId) throws Exception {
- Channel channel = senderConnection.createChannel();
-
- AMQP.BasicProperties.Builder propertiesBuilder = new AMQP.BasicProperties().builder();
-
- Map headers = new HashMap<>();
-
- if (properties != null) {
- properties.forEach((name, value) -> {
- String stringValue = value instanceof String ? (String) value : null;
- if ("messageId".equals(name)) {
- propertiesBuilder.messageId(stringValue);
- } else if ("contentType".equals(name)) {
- propertiesBuilder.contentType(stringValue);
- } else if ("contentEncoding".equals(name)) {
- propertiesBuilder.contentEncoding(stringValue);
- } else if ("correlationId".equals(name)) {
- propertiesBuilder.correlationId(stringValue);
- } else if ("replyTo".equals(name)) {
- propertiesBuilder.replyTo(stringValue);
- } else if ("timestamp".equals(name)) {
- try {
- DateFormat formatter = new SimpleDateFormat("dd-MM-yyyy,HH:mm:ss SSS");
- propertiesBuilder.timestamp(formatter.parse(stringValue));
- } catch (ParseException e) {
- log.error("{}", e);
- }
- } else {
- headers.put(name, value);
- }
- });
- }
-
- channel.basicPublish("", queueName, propertiesBuilder.headers(headers).build(), message.getBytes());
- channel.close();
- }
-
- @Override
- Connection getConnection() {
- throw new UnsupportedOperationException("getConnection unsupported in RabbitMqManager. RabbitMQ using custom implementation.");
- }
-
- @Override
- public void close() throws IOException {
- senderConnection.close();
- }
-
- @Override
- public Message waitMessage(String queueName, Long timeoutMs, String testIdHeaderName, String testId) throws JMSException {
- try {
- Channel channel = senderConnection.createChannel();
- final QueueingConsumer consumer = new QueueingConsumer(channel);
- channel.basicConsume(queueName, true, consumer);
-
- QueueingConsumer.Delivery delivery = consumer.nextDelivery(timeoutMs);
- if (delivery != null) {
- channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
- RMQTextMessage message = new RMQTextMessage();
- message.setText(new String(delivery.getBody()));
- return message;
- }
- return null;
- } catch (IOException | InterruptedException e) {
- log.error("RabbitMQ waitMessage error: {}", e);
- }
- return null;
- }
-}
+/*
+ * Copyright 2018 BSC Msc, LLC
+ *
+ * This file is part of the AuTe Framework project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ru.bsc.test.at.executor.mq;
+
+import com.rabbitmq.client.AMQP;
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.ConnectionFactory;
+import com.rabbitmq.client.QueueingConsumer;
+import com.rabbitmq.jms.client.message.RMQTextMessage;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+import java.util.Map;
+
+// Это реализация для работы с RabbitMQ.
+// Сообщения не отправляются в очередь через стандартные интерфейсы JMS, если у очереди указан параметр x-dead-letter-exchange
+
+@Slf4j
+class RabbitMqManager extends AbstractMqManager {
+
+ private final com.rabbitmq.client.Connection senderConnection;
+
+ RabbitMqManager(String host, int port, String username, String password) throws JMSException {
+
+ try {
+ ConnectionFactory connectionFactory = new ConnectionFactory();
+ connectionFactory.setHost(host);
+ connectionFactory.setPort(port);
+ connectionFactory.setUsername(username);
+ connectionFactory.setPassword(password);
+ senderConnection = connectionFactory.newConnection();
+ } catch (Exception e) {
+ log.error("{}", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void sendTextMessage(String queueName, String message, Map properties, String testIdHeaderName, String testId) throws Exception {
+ Channel channel = senderConnection.createChannel();
+
+ AMQP.BasicProperties.Builder propertiesBuilder = new AMQP.BasicProperties().builder();
+
+ Map headers = new HashMap<>();
+
+ if (properties != null) {
+ properties.forEach((name, value) -> {
+ String stringValue = value instanceof String ? (String) value : null;
+ if ("messageId".equals(name)) {
+ propertiesBuilder.messageId(stringValue);
+ } else if ("contentType".equals(name)) {
+ propertiesBuilder.contentType(stringValue);
+ } else if ("contentEncoding".equals(name)) {
+ propertiesBuilder.contentEncoding(stringValue);
+ } else if ("correlationId".equals(name)) {
+ propertiesBuilder.correlationId(stringValue);
+ } else if ("replyTo".equals(name)) {
+ propertiesBuilder.replyTo(stringValue);
+ } else if ("timestamp".equals(name)) {
+ try {
+ DateFormat formatter = new SimpleDateFormat("dd-MM-yyyy,HH:mm:ss SSS");
+ propertiesBuilder.timestamp(formatter.parse(stringValue));
+ } catch (ParseException e) {
+ log.error("{}", e);
+ }
+ } else {
+ headers.put(name, value);
+ }
+ });
+ }
+
+ channel.basicPublish("", queueName, propertiesBuilder.headers(headers).build(), message.getBytes(StandardCharsets.UTF_8));
+ channel.close();
+ }
+
+ @Override
+ Connection getConnection() {
+ throw new UnsupportedOperationException("getConnection unsupported in RabbitMqManager. RabbitMQ using custom implementation.");
+ }
+
+ @Override
+ public void close() throws IOException {
+ senderConnection.close();
+ }
+
+ @Override
+ public Message waitMessage(String queueName, Long timeoutMs, String testIdHeaderName, String testId) throws JMSException {
+ try {
+ Channel channel = senderConnection.createChannel();
+ final QueueingConsumer consumer = new QueueingConsumer(channel);
+ channel.basicConsume(queueName, true, consumer);
+
+ QueueingConsumer.Delivery delivery = consumer.nextDelivery(timeoutMs);
+ if (delivery != null) {
+ channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
+ RMQTextMessage message = new RMQTextMessage();
+ message.setText(new String(delivery.getBody(), StandardCharsets.UTF_8));
+ return message;
+ }
+ return null;
+ } catch (IOException | InterruptedException e) {
+ log.error("RabbitMQ waitMessage error: {}", e);
+ }
+ return null;
+ }
+}
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/ExecutorUtils.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/ExecutorUtils.java
index f15c52cf..d2b7ccfc 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/ExecutorUtils.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/ExecutorUtils.java
@@ -201,7 +201,7 @@ static void executeSql(Connection connection, Step step, Map sce
List queryList = new LinkedList<>();
stepResult.setSqlQueryList(queryList);
for (SqlData sqlData : step.getSqlDataList()) {
- if (StringUtils.isNotEmpty(sqlData.getSql()) && StringUtils.isNotEmpty(sqlData.getSqlSavedParameter())) {
+ if (isNotEmpty(sqlData.getSql()) && isNotEmpty(sqlData.getSqlSavedParameter())) {
String query = evaluateExpressions(sqlData.getSql(), scenarioVariables);
queryList.add(query);
try (NamedParameterStatement statement = new NamedParameterStatement(connection, query)) {
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/MqStepExecutor.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/MqStepExecutor.java
index 063c62d8..fa7a8df6 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/MqStepExecutor.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/MqStepExecutor.java
@@ -70,9 +70,9 @@ public void execute(WireMockAdmin wireMockAdmin, Connection connection, Stand st
for (int repetitionCounter = 0; repetitionCounter < numberRepetitions; repetitionCounter++) {
StepRequester stepRequester;
if (step.getUsePolling()) {
- stepRequester = new MqPollingStepRequester(stepResult, step, requestBody, testId, project, mqClient, scenarioVariables, projectPath);
+ stepRequester = new MqPollingStepRequester(stepResult, step, requestBody, testId, project, mqClient, scenarioVariables);
} else {
- stepRequester = new MqSimpleStepRequester(stepResult, step, requestBody, testId, project, mqClient, scenarioVariables, projectPath);
+ stepRequester = new MqSimpleStepRequester(stepResult, step, requestBody, testId, project, mqClient, scenarioVariables);
}
stepRequester.request();
}
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/RestStepExecutor.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/RestStepExecutor.java
index 95b93d73..6f2eea65 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/RestStepExecutor.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/RestStepExecutor.java
@@ -91,7 +91,7 @@ public void execute(WireMockAdmin wireMockAdmin, Connection connection, Stand st
for (int repetitionCounter = 0; repetitionCounter < numberRepetitions; repetitionCounter++) {
// COM-123 Timeout
- if (step.getTimeoutMs() != null && !step.getTimeoutMs().isEmpty() && (step.isTimeoutEachRepetition())) {
+ if (step.getTimeoutMs() != null && !step.getTimeoutMs().isEmpty() && step.isTimeoutEachRepetition()) {
delayUtilities.delay(parseLongOrVariable(scenarioVariables, step.getTimeoutMs(), 0));
}
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/requester/MqAbstractStepRequester.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/requester/MqAbstractStepRequester.java
index 69f037f4..01fb3c77 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/requester/MqAbstractStepRequester.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/requester/MqAbstractStepRequester.java
@@ -48,9 +48,8 @@ public abstract class MqAbstractStepRequester implements StepRequester {
protected String requestBody;
protected String testId;
protected Project project;
- protected MqClient mqClient;
+ private MqClient mqClient;
protected Map scenarioVariables;
- protected String projectPath;
protected abstract ClientResponse call() throws Exception;
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/requester/MqPollingStepRequester.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/requester/MqPollingStepRequester.java
index 165a1217..2d14b850 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/requester/MqPollingStepRequester.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/requester/MqPollingStepRequester.java
@@ -35,8 +35,8 @@
@Slf4j
public class MqPollingStepRequester extends MqAbstractStepRequester {
- public MqPollingStepRequester(StepResult stepResult, Step step, String requestBody, String testId, Project project, MqClient mqClient, Map scenarioVariables, String projectPath) {
- super(stepResult, step, requestBody, testId, project, mqClient, scenarioVariables, projectPath);
+ public MqPollingStepRequester(StepResult stepResult, Step step, String requestBody, String testId, Project project, MqClient mqClient, Map scenarioVariables) {
+ super(stepResult, step, requestBody, testId, project, mqClient, scenarioVariables);
}
@Override
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/requester/MqSimpleStepRequester.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/requester/MqSimpleStepRequester.java
index b940a5e0..80719ce6 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/requester/MqSimpleStepRequester.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/requester/MqSimpleStepRequester.java
@@ -31,8 +31,8 @@
*/
public class MqSimpleStepRequester extends MqAbstractStepRequester {
- public MqSimpleStepRequester(StepResult stepResult, Step step, String requestBody, String testId, Project project, MqClient mqClient, Map scenarioVariables, String projectPath) {
- super(stepResult, step, requestBody, testId, project, mqClient, scenarioVariables, projectPath);
+ public MqSimpleStepRequester(StepResult stepResult, Step step, String requestBody, String testId, Project project, MqClient mqClient, Map scenarioVariables) {
+ super(stepResult, step, requestBody, testId, project, mqClient, scenarioVariables);
}
@Override
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/requester/RequesterUtils.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/requester/RequesterUtils.java
index 214bf8b5..0f8e550e 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/requester/RequesterUtils.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/requester/RequesterUtils.java
@@ -147,7 +147,7 @@ static boolean tryUsePolling(Step step, ClientResponse clientResponse) {
}
boolean retry = true;
try {
- if (StringUtils.isNotEmpty(content) && JsonPath.read(content, step.getPollingJsonXPath()) != null) {
+ if (isNotEmpty(content) && JsonPath.read(content, step.getPollingJsonXPath()) != null) {
log.info("Required attribute for polling found in path {}. Stop polling", step.getPollingJsonXPath());
retry = false;
}
@@ -175,8 +175,8 @@ static long calculateNextPollingDelay(long prevDelay) {
private static void jsonComparing(String expectedResponse, String responseContent, String jsonCompareMode) throws Exception {
log.debug("Json comparing {} {} {}", expectedResponse, responseContent, jsonCompareMode);
- if ((StringUtils.isNotEmpty(expectedResponse) || StringUtils.isNotEmpty(responseContent)) &&
- (!responseContent.equals(expectedResponse))) {
+ if ((isNotEmpty(expectedResponse) || isNotEmpty(responseContent)) &&
+ !responseContent.equals(expectedResponse)) {
try {
JSONAssert.assertEquals(
expectedResponse == null ? "" : expectedResponse.replaceAll(" ", " "),
diff --git a/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/requester/RestAbstractStepRequester.java b/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/requester/RestAbstractStepRequester.java
index ee172563..52f5fa78 100644
--- a/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/requester/RestAbstractStepRequester.java
+++ b/atf-executor/src/main/java/ru/bsc/test/at/executor/step/executor/requester/RestAbstractStepRequester.java
@@ -86,7 +86,7 @@ public void request() throws Exception {
// 6.2. Проверить код статуса ответа
Integer expectedStatusCode = step.getExpectedStatusCode();
log.debug("Expected status is {} and actual status is {}", expectedStatusCode, responseData.getStatusCode());
- if ((expectedStatusCode != null) && (expectedStatusCode != responseData.getStatusCode())) {
+ if (expectedStatusCode != null && expectedStatusCode != responseData.getStatusCode()) {
throw new Exception(String.format(
"Expected status code: %d. Actual status code: %d",
expectedStatusCode,
diff --git a/atf-wiremock/pom.xml b/atf-wiremock/pom.xml
index 3a2e7d69..b75e2687 100644
--- a/atf-wiremock/pom.xml
+++ b/atf-wiremock/pom.xml
@@ -180,6 +180,55 @@
org.springframework.boot
spring-boot-maven-plugin
+
+
+ org.codehaus.mojo
+ findbugs-maven-plugin
+ 3.0.5
+
+ Max
+
+ medium
+
+ true
+ FindReturnRef,RuntimeExceptionCapture
+
+
+
+ analyze-compile
+ compile
+
+ check
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/atf-wiremock/src/main/java/ru/bsc/test/at/mock/filter/utils/MultipartToBase64ConverterServletRequest.java b/atf-wiremock/src/main/java/ru/bsc/test/at/mock/filter/utils/MultipartToBase64ConverterServletRequest.java
index 9faeccfb..1d4a58ca 100644
--- a/atf-wiremock/src/main/java/ru/bsc/test/at/mock/filter/utils/MultipartToBase64ConverterServletRequest.java
+++ b/atf-wiremock/src/main/java/ru/bsc/test/at/mock/filter/utils/MultipartToBase64ConverterServletRequest.java
@@ -133,7 +133,7 @@ public ServletInputStream getInputStream() throws IOException {
if (rawData == null) {
initialize();
}
- return new ByteArrayServletInputStream((rawData));
+ return new ByteArrayServletInputStream(rawData);
}
@Override
@@ -141,7 +141,7 @@ public BufferedReader getReader() throws IOException {
if (rawData == null) {
initialize();
}
- return new BufferedReader(new InputStreamReader(new ByteArrayServletInputStream((rawData))));
+ return new BufferedReader(new InputStreamReader(new ByteArrayServletInputStream(rawData), StandardCharsets.UTF_8));
}
private MultiMap getParams(){
@@ -224,7 +224,9 @@ private void processEvalField(List fileItems, ConvertedRequestBody con
if(configProperties.isBoundaryStaticEnabled()) {
changeMultipartHeaderBoundary(convertedRequestBody.getStaticBoundary());
}
- rawData = convertedRequestBody.getAllDataBody().toString().replace(EVAL_FIELD, convertedRequestBody.getStaticBoundary()).getBytes();
+ rawData = convertedRequestBody.getAllDataBody()
+ .toString()
+ .replace(EVAL_FIELD, convertedRequestBody.getStaticBoundary()).getBytes(StandardCharsets.UTF_8);
}
}
@@ -271,9 +273,9 @@ private String buildPartMultipartRequest(String staticBoundary, FileItem fileIte
byte[] file = fileItem.get();
if(!org.apache.commons.codec.binary.Base64.isArrayByteBase64(file)) {
- multipart.append(new String(Base64.getEncoder().encode(file)));
+ multipart.append(new String(Base64.getEncoder().encode(file), StandardCharsets.UTF_8));
} else {
- multipart.append(new String(file));
+ multipart.append(new String(file, StandardCharsets.UTF_8));
}
multipart.append(NEW_LINE);
if(file.length == 0 && countHeaderIterm == 0) {
@@ -282,7 +284,7 @@ private String buildPartMultipartRequest(String staticBoundary, FileItem fileIte
return multipart.toString();
}
- private class ByteArrayServletInputStream extends ServletInputStream {
+ private static class ByteArrayServletInputStream extends ServletInputStream {
private final ByteArrayInputStream inputStream;
diff --git a/atf-wiremock/src/main/java/ru/bsc/test/at/mock/mq/worker/ActiveMQWorker.java b/atf-wiremock/src/main/java/ru/bsc/test/at/mock/mq/worker/ActiveMQWorker.java
index 50f991b0..76c8ba59 100644
--- a/atf-wiremock/src/main/java/ru/bsc/test/at/mock/mq/worker/ActiveMQWorker.java
+++ b/atf-wiremock/src/main/java/ru/bsc/test/at/mock/mq/worker/ActiveMQWorker.java
@@ -98,15 +98,17 @@ public void run() {
for (MockMessageResponse mockResponse : mockMessage.getResponses()) {
byte[] response;
- if (StringUtils.isNotEmpty(mockResponse.getResponseBody())) {
- response = transformer.transform(mockMessage.getGuid(), stringBody, extractor.createContext(message), mockResponse.getResponseBody()).getBytes();
- } else if (StringUtils.isNotEmpty(mockMessage.getHttpUrl())) {
+ if (isNotEmpty(mockResponse.getResponseBody())) {
+ response = transformer.transform(mockMessage.getGuid(), stringBody, extractor.createContext(message), mockResponse.getResponseBody()).getBytes(StandardCharsets.UTF_8);
+ } else if (isNotEmpty(mockMessage.getHttpUrl())) {
try (HttpClient httpClient = new HttpClient()) {
- response = httpClient.sendPost(mockMessage.getHttpUrl(), new String(message.getContent().getData(), StandardCharsets.UTF_8), getTestIdHeaderName(), testId).getBytes();
+ response = httpClient.sendPost(mockMessage.getHttpUrl(),
+ new String(message.getContent().getData(), StandardCharsets.UTF_8),
+ getTestIdHeaderName(), testId).getBytes(StandardCharsets.UTF_8);
}
mockedRequest.setHttpRequestUrl(mockMessage.getHttpUrl());
} else {
- response = stringBody.getBytes();
+ response = stringBody.getBytes(StandardCharsets.UTF_8);
}
mockedRequest.setDestinationQueue(mockResponse.getDestinationQueueName());
diff --git a/atf-wiremock/src/main/java/ru/bsc/test/at/mock/mq/worker/IbmMQWorker.java b/atf-wiremock/src/main/java/ru/bsc/test/at/mock/mq/worker/IbmMQWorker.java
index ec847cd5..e5697cda 100644
--- a/atf-wiremock/src/main/java/ru/bsc/test/at/mock/mq/worker/IbmMQWorker.java
+++ b/atf-wiremock/src/main/java/ru/bsc/test/at/mock/mq/worker/IbmMQWorker.java
@@ -77,7 +77,7 @@ public void run() {
connectionFactory.setHostName(getBrokerUrl());
connectionFactory.setPort(port);
connectionFactory.setTransportType(WMQ_CM_CLIENT);
- if (StringUtils.isNotEmpty(channel)) {
+ if (isNotEmpty(channel)) {
connectionFactory.setChannel(channel);
}
} catch (JMSException e) {
@@ -134,15 +134,15 @@ public void run() {
for (MockMessageResponse mockResponse : mockMessage.getResponses()) {
byte[] response;
- if (StringUtils.isNotEmpty(mockResponse.getResponseBody())) {
- response = transformer.transform(mockMessage.getGuid(), stringBody, extractor.createContext(receivedMessage), mockResponse.getResponseBody()).getBytes();
- } else if (StringUtils.isNotEmpty(mockMessage.getHttpUrl())) {
+ if (isNotEmpty(mockResponse.getResponseBody())) {
+ response = transformer.transform(mockMessage.getGuid(), stringBody, extractor.createContext(receivedMessage), mockResponse.getResponseBody()).getBytes(StandardCharsets.UTF_8);
+ } else if (isNotEmpty(mockMessage.getHttpUrl())) {
try (HttpClient httpClient = new HttpClient()) {
- response = httpClient.sendPost(mockMessage.getHttpUrl(), stringBody, getTestIdHeaderName(), testId).getBytes();
+ response = httpClient.sendPost(mockMessage.getHttpUrl(), stringBody, getTestIdHeaderName(), testId).getBytes(StandardCharsets.UTF_8);
}
mockedRequest.setHttpRequestUrl(mockMessage.getHttpUrl());
} else {
- response = stringBody.getBytes();
+ response = stringBody.getBytes(StandardCharsets.UTF_8);
}
String destinationQueue = isNotEmpty(mockResponse.getDestinationQueueName())
@@ -162,7 +162,7 @@ public void run() {
consumer.close();
session.close();
connection.close();
- } catch (Exception e) {
+ } catch (JMSException e) {
log.error("Caught:", e);
}
}
@@ -186,7 +186,7 @@ private void sendMessage(Session session, Message receivedMessage, MockedRequest
// Переслать сообщение в очередь-назначение
producer.send(newMessage);
producer.close();
- log.info(" [x] Send >>> {} '{}'", destinationQueue, messageBody);
+ log.info(" [x] Send >>> {} '{}'", destinationQueue, messageBody, StandardCharsets.UTF_8);
} else {
log.info(" [x] Send >>> ***black hole***");
}
diff --git a/atf-wiremock/src/main/java/ru/bsc/test/at/mock/mq/worker/RabbitMQWorker.java b/atf-wiremock/src/main/java/ru/bsc/test/at/mock/mq/worker/RabbitMQWorker.java
index 03abc371..4bfb4b68 100644
--- a/atf-wiremock/src/main/java/ru/bsc/test/at/mock/mq/worker/RabbitMQWorker.java
+++ b/atf-wiremock/src/main/java/ru/bsc/test/at/mock/mq/worker/RabbitMQWorker.java
@@ -18,11 +18,7 @@
package ru.bsc.test.at.mock.mq.worker;
-import com.rabbitmq.client.AMQP;
-import com.rabbitmq.client.Channel;
-import com.rabbitmq.client.ConnectionFactory;
-import com.rabbitmq.client.DefaultConsumer;
-import com.rabbitmq.client.Envelope;
+import com.rabbitmq.client.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.Buffer;
import org.apache.commons.lang3.StringUtils;
@@ -44,7 +40,7 @@ public class RabbitMQWorker extends AbstractMqWorker {
private final Buffer fifo;
private Channel channelFrom;
private Channel channelTo;
- private com.rabbitmq.client.Connection connection;
+ private Connection connection;
private int port;
private VelocityTransformer velocityTransformer;
@@ -115,10 +111,10 @@ public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProp
byte[] response;
if (StringUtils.isNotEmpty(mockResponse.getResponseBody())) {
- response = velocityTransformer.transform(mockMessage.getGuid(), stringBody, null, mockResponse.getResponseBody()).getBytes();
+ response = velocityTransformer.transform(mockMessage.getGuid(), stringBody, null, mockResponse.getResponseBody()).getBytes(StandardCharsets.UTF_8);
} else if (StringUtils.isNotEmpty(mockMessage.getHttpUrl())) {
try (HttpClient httpClient = new HttpClient()) {
- response = httpClient.sendPost(mockMessage.getHttpUrl(), new String(body, StandardCharsets.UTF_8), getTestIdHeaderName(), testId).getBytes();
+ response = httpClient.sendPost(mockMessage.getHttpUrl(), new String(body, StandardCharsets.UTF_8), getTestIdHeaderName(), testId).getBytes(StandardCharsets.UTF_8);
}
mockedRequest.setHttpRequestUrl(mockMessage.getHttpUrl());
} else {
diff --git a/atf-wiremock/src/main/java/ru/bsc/test/at/mock/wiremock/transformers/CustomVelocityResponseTransformer.java b/atf-wiremock/src/main/java/ru/bsc/test/at/mock/wiremock/transformers/CustomVelocityResponseTransformer.java
index d2265e20..f2d2fc98 100644
--- a/atf-wiremock/src/main/java/ru/bsc/test/at/mock/wiremock/transformers/CustomVelocityResponseTransformer.java
+++ b/atf-wiremock/src/main/java/ru/bsc/test/at/mock/wiremock/transformers/CustomVelocityResponseTransformer.java
@@ -44,6 +44,7 @@
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -145,26 +146,26 @@ private byte[] parseConvertBody(String body) {
// обрабатываем каждую часть multipart'a - ищем base64
for (String part : partsMultipart) {
if (isNotBoundaryAndInBase64(part)) {
- result = Base64.decode(part.getBytes());
+ result = Base64.decode(part.getBytes(StandardCharsets.UTF_8));
} else {
- result = part.getBytes();
+ result = part.getBytes(StandardCharsets.UTF_8);
}
buffer.addAll(Arrays.asList(ArrayUtils.toObject(result)));
}
- result = ArrayUtils.toPrimitive((Byte[]) buffer.toArray());
+ result = ArrayUtils.toPrimitive(buffer.toArray(new Byte[buffer.size()]));
} else {
// если не начинается с boundary - проверяем, не base64 лежит в корне
- if (isBase64(body.getBytes())) {
- result = Base64.decode(body.getBytes());
+ if (isBase64(body.getBytes(StandardCharsets.UTF_8))) {
+ result = Base64.decode(body.getBytes(StandardCharsets.UTF_8));
} else {
- result = body.getBytes();
+ result = body.getBytes(StandardCharsets.UTF_8);
}
}
return result;
}
private boolean isNotBoundaryAndInBase64(String part) {
- return !part.startsWith(DOUBLE_DASH) && isBase64(part.getBytes());
+ return !part.startsWith(DOUBLE_DASH) && isBase64(part.getBytes(StandardCharsets.UTF_8));
}
private Boolean templateDeclared(final ResponseDefinition response) {
diff --git a/atf-wiremock/src/main/java/ru/bsc/test/at/mock/wiremock/webcontextlistener/configuration/CustomWarConfiguration.java b/atf-wiremock/src/main/java/ru/bsc/test/at/mock/wiremock/webcontextlistener/configuration/CustomWarConfiguration.java
index 1e5f06e3..6b2a228c 100644
--- a/atf-wiremock/src/main/java/ru/bsc/test/at/mock/wiremock/webcontextlistener/configuration/CustomWarConfiguration.java
+++ b/atf-wiremock/src/main/java/ru/bsc/test/at/mock/wiremock/webcontextlistener/configuration/CustomWarConfiguration.java
@@ -64,7 +64,7 @@ public Map extensionsOfType(Class extensionT
properties.setProperty("resource.loader", "file");
properties.setProperty("file.resource.loader.path", "." + File.separator + "velocity");
- try (final InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(VELOCITY_PROPERTIES.getValue())) {
+ try (InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(VELOCITY_PROPERTIES.getValue())) {
properties.load(stream);
} catch (Exception e) {
log.warn("Error while loading properties: {}. Using default values", VELOCITY_PROPERTIES.getValue());
diff --git a/pom.xml b/pom.xml
index 77c50a3a..0066ea65 100644
--- a/pom.xml
+++ b/pom.xml
@@ -169,6 +169,61 @@
org.apache.maven.plugins
maven-release-plugin
+
+ org.apache.maven.plugins
+ maven-pmd-plugin
+ 3.12.0
+
+
+ UTF-8
+ 30
+ 1.8
+ true
+
+ **/*StepRoMapperImpl.java
+
+
+
+ target/generated-sources/annotations
+
+
+
+
+ analyze-compile
+ compile
+
+ pmd
+
+
+ check
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 3.1.0
+
+
+
+
+
+ true
+ checkstyleOutput.txt
+
+
+
+
+ analyze-compile
+ compile
+
+ checkstyle
+
+
+
+
\ No newline at end of file
diff --git a/rulesets/exclude-pmd.properties b/rulesets/exclude-pmd.properties
new file mode 100644
index 00000000..763c69df
--- /dev/null
+++ b/rulesets/exclude-pmd.properties
@@ -0,0 +1,17 @@
+#
+# AuTe Framework project
+# Copyright 2018 BSC Msc, LLC
+#
+# ATF project is licensed under
+# The Apache 2.0 License
+# http://www.apache.org/licenses/LICENSE-2.0.html
+#
+# For more information visit http://www.bsc-ideas.com/ru/
+#
+# Files ru.bsc.test.autotester.diff.DiffMatchPatch.java, ru.bsc.test.autotester.diff.Diff.java,
+# ru.bsc.test.autotester.diff.LinesToCharsResult, ru.bsc.test.autotester.diff.Operation,
+# ru.bsc.test.autotester.diff.Patch
+# are copied from https://github.com/google/diff-match-patch
+#
+
+ru.bsc.test.autotester.mapper.StepRoMapperImpl=UnnecessaryFullyQualifiedName
\ No newline at end of file