Implement preview fix endpoint

Change-Id: I70366164d89bb798b435e31d2670261676b9fc04
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 5055007..d6a7092 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -5356,6 +5356,17 @@
 The `context` parameter can be specified to control the number of lines of surrounding context
 in the diff.  Valid values are `ALL` or number of lines.
 
+[[preview-fix]]
+=== Preview fix
+--
+'GET /changes/<<change-id,\{change-id\}>>/revisions/<<revision-id,\{revision-id\}>>/fixes/<<fix-id,\{fix-id\}>>/preview'
+--
+
+Gets the diffs of all files for a certain <<fix-id,\{fix-id\}>>.
+As response, a map of link:#diff-info[DiffInfo] entities is returned that describes the diffs.
+
+Each link:#diff-info[DiffInfo] is the differences between the patch set indicated by revision-id and a virtual patch set with the applied fix.
+
 [[get-blame]]
 === Get Blame
 --
diff --git a/java/com/google/gerrit/acceptance/BUILD b/java/com/google/gerrit/acceptance/BUILD
index cb3524a..e4a993c 100644
--- a/java/com/google/gerrit/acceptance/BUILD
+++ b/java/com/google/gerrit/acceptance/BUILD
@@ -90,6 +90,7 @@
         "//java/com/google/gerrit/pgm:daemon",
         "//java/com/google/gerrit/pgm/http/jetty",
         "//java/com/google/gerrit/pgm/util",
+        "//java/com/google/gerrit/server/fixes/testing",
         "//java/com/google/gerrit/server/group/testing",
         "//java/com/google/gerrit/server/project/testing:project-test-util",
         "//java/com/google/gerrit/testing:gerrit-test-util",
diff --git a/java/com/google/gerrit/extensions/common/testing/DiffInfoSubject.java b/java/com/google/gerrit/extensions/common/testing/DiffInfoSubject.java
index 8853a30..e258134 100644
--- a/java/com/google/gerrit/extensions/common/testing/DiffInfoSubject.java
+++ b/java/com/google/gerrit/extensions/common/testing/DiffInfoSubject.java
@@ -18,12 +18,15 @@
 import static com.google.gerrit.extensions.common.testing.FileMetaSubject.fileMetas;
 import static com.google.gerrit.truth.ListSubject.elements;
 
+import com.google.common.truth.BooleanSubject;
 import com.google.common.truth.ComparableSubject;
 import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.IterableSubject;
 import com.google.common.truth.Subject;
 import com.google.gerrit.extensions.common.ChangeType;
 import com.google.gerrit.extensions.common.DiffInfo;
 import com.google.gerrit.extensions.common.DiffInfo.ContentEntry;
+import com.google.gerrit.extensions.common.DiffInfo.IntraLineStatus;
 import com.google.gerrit.truth.ListSubject;
 
 public class DiffInfoSubject extends Subject {
@@ -60,4 +63,24 @@
     isNotNull();
     return check("metaB").about(fileMetas()).that(diffInfo.metaB);
   }
+
+  public ComparableSubject<IntraLineStatus> intralineStatus() {
+    isNotNull();
+    return check("intralineStatus").that(diffInfo.intralineStatus);
+  }
+
+  public IterableSubject webLinks() {
+    isNotNull();
+    return check("webLinks").that(diffInfo.webLinks);
+  }
+
+  public BooleanSubject binary() {
+    isNotNull();
+    return check("binary").that(diffInfo.binary);
+  }
+
+  public IterableSubject diffHeader() {
+    isNotNull();
+    return check("diffHeader").that(diffInfo.diffHeader);
+  }
 }
diff --git a/java/com/google/gerrit/extensions/common/testing/FileMetaSubject.java b/java/com/google/gerrit/extensions/common/testing/FileMetaSubject.java
index fb09a1f..0953bfe 100644
--- a/java/com/google/gerrit/extensions/common/testing/FileMetaSubject.java
+++ b/java/com/google/gerrit/extensions/common/testing/FileMetaSubject.java
@@ -18,6 +18,8 @@
 
 import com.google.common.truth.FailureMetadata;
 import com.google.common.truth.IntegerSubject;
+import com.google.common.truth.IterableSubject;
+import com.google.common.truth.StringSubject;
 import com.google.common.truth.Subject;
 import com.google.gerrit.extensions.common.DiffInfo.FileMeta;
 
@@ -42,4 +44,24 @@
     isNotNull();
     return check("totalLineCount()").that(fileMeta.lines);
   }
+
+  public StringSubject name() {
+    isNotNull();
+    return check("name").that(fileMeta.name);
+  }
+
+  public StringSubject commitId() {
+    isNotNull();
+    return check("commitId").that(fileMeta.commitId);
+  }
+
+  public StringSubject contentType() {
+    isNotNull();
+    return check("contentType").that(fileMeta.contentType);
+  }
+
+  public IterableSubject webLinks() {
+    isNotNull();
+    return check("webLinks").that(fileMeta.webLinks);
+  }
 }
diff --git a/java/com/google/gerrit/server/config/GerritGlobalModule.java b/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 2a0466f..edf8864 100644
--- a/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -158,6 +158,7 @@
 import com.google.gerrit.server.notedb.NoteDbModule;
 import com.google.gerrit.server.patch.PatchListCacheImpl;
 import com.google.gerrit.server.patch.PatchScriptFactory;
+import com.google.gerrit.server.patch.PatchScriptFactoryForAutoFix;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.permissions.PermissionCollection;
 import com.google.gerrit.server.permissions.SectionSortCache;
@@ -261,6 +262,7 @@
     factory(LabelsJson.Factory.class);
     factory(MergeUtil.Factory.class);
     factory(PatchScriptFactory.Factory.class);
+    factory(PatchScriptFactoryForAutoFix.Factory.class);
     factory(ProjectState.Factory.class);
     factory(RevisionJson.Factory.class);
     factory(InboundEmailRejectionSender.Factory.class);
diff --git a/java/com/google/gerrit/server/fixes/FixCalculator.java b/java/com/google/gerrit/server/fixes/FixCalculator.java
new file mode 100644
index 0000000..5d9d2c2
--- /dev/null
+++ b/java/com/google/gerrit/server/fixes/FixCalculator.java
@@ -0,0 +1,366 @@
+// Copyright (C) 2019 The Android Open Source 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 com.google.gerrit.server.fixes;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.FixReplacement;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.jgit.diff.ReplaceEdit;
+import com.google.gerrit.server.patch.Text;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import org.eclipse.jgit.diff.Edit;
+
+/**
+ * Produces final version of an input content with all fixes applied together with list of edits.
+ */
+public class FixCalculator {
+  private static final Comparator<FixReplacement> ASC_RANGE_FIX_REPLACEMENT_COMPARATOR =
+      Comparator.comparing(fixReplacement -> fixReplacement.range);
+
+  private FixCalculator() {}
+
+  /**
+   * Returns a result of applying fixes to an original content.
+   *
+   * @param originalContent is a text to which fixes must be applied
+   * @param fixReplacements is a list of fixes to be applied
+   * @throws ResourceConflictException if the fixReplacements contains invalid data (for example, if
+   *     an item points to an invalid range or if some ranges are intersected).
+   */
+  public static String getNewFileContent(
+      String originalContent, List<FixReplacement> fixReplacements)
+      throws ResourceConflictException {
+    FixResult fixResult = calculateFix(new Text(originalContent.getBytes(UTF_8)), fixReplacements);
+    return fixResult.text.getString(0, fixResult.text.size(), false);
+  }
+
+  /**
+   * Returns a result of applying fixes to an original content and list of applied edits.
+   *
+   * @param originalText is a text to which fixes must be applied
+   * @param fixReplacements is a list of fixes to be applied
+   * @return {@link FixResult}
+   * @throws ResourceConflictException if the fixReplacements contains invalid data (for example, if
+   *     an item points to an invalid range or if some ranges are intersected).
+   */
+  public static FixResult calculateFix(Text originalText, List<FixReplacement> fixReplacements)
+      throws ResourceConflictException {
+    List<FixReplacement> sortedReplacements = new ArrayList<>(fixReplacements);
+    sortedReplacements.sort(ASC_RANGE_FIX_REPLACEMENT_COMPARATOR);
+    if (!sortedReplacements.isEmpty() && sortedReplacements.get(0).range.startLine <= 0) {
+      throw new ResourceConflictException(
+          String.format(
+              "Cannot calculate fix replacement for range %s",
+              toString(sortedReplacements.get(0).range)));
+    }
+    ContentBuilder builder = new ContentBuilder(originalText);
+    for (FixReplacement fixReplacement : sortedReplacements) {
+      try {
+        builder.addReplacement(fixReplacement);
+      } catch (IndexOutOfBoundsException e) {
+        throw new ResourceConflictException(
+            String.format(
+                "Cannot calculate fix replacement for range %s", toString(fixReplacement.range)),
+            e);
+      }
+    }
+    return builder.build();
+  }
+
+  private static String toString(Comment.Range range) {
+    return String.format(
+        "(%s:%s - %s:%s)", range.startLine, range.startChar, range.endLine, range.endChar);
+  }
+
+  private static class ContentBuilder {
+    private static class FixRegion {
+      int startSrcLine;
+      int startDstLine;
+      int startSrcPos;
+      int startDstPos;
+      List<Edit> internalEdits;
+
+      FixRegion() {
+        this.internalEdits = new ArrayList<>();
+      }
+    }
+
+    private final ContentProcessor contentProcessor;
+    final ImmutableList.Builder<Edit> edits;
+    FixRegion currentRegion;
+
+    ContentBuilder(Text src) {
+      this.contentProcessor = new ContentProcessor(src);
+      this.edits = new ImmutableList.Builder<>();
+    }
+
+    void addReplacement(FixReplacement replacement) {
+      if (shouldStartNewEdit(replacement)) {
+        finishExistingEdit();
+      }
+      // processSrcContent expects that line number is 0-based,
+      // but replacement.range.startLine is 1-based, so subtract 1
+      processSrcContent(replacement.range.startLine - 1, replacement.range.startChar, true);
+      processReplacement(replacement);
+    }
+
+    Text getNewText() {
+      return new Text(contentProcessor.sb.toString().getBytes(UTF_8));
+    }
+
+    void finish() {
+      finishExistingEdit();
+      if (contentProcessor.hasMoreLines()) {
+        contentProcessor.appendLinesToEndOfContent();
+      }
+    }
+
+    public FixResult build() {
+      finish();
+      return new FixResult(edits.build(), this.getNewText());
+    }
+
+    private void finishExistingEdit() {
+      if (contentProcessor.srcPosition.column > 0 || contentProcessor.dstPosition.column > 0) {
+        contentProcessor.processToEndOfLine(true);
+      }
+      if (currentRegion != null) {
+        int endSrc = contentProcessor.srcPosition.line;
+        if (contentProcessor.srcPosition.column > 0) {
+          endSrc++;
+        }
+        int endDst = contentProcessor.dstPosition.line;
+        if (contentProcessor.dstPosition.column > 0) {
+          endDst++;
+        }
+        ReplaceEdit edit =
+            new ReplaceEdit(
+                currentRegion.startSrcLine,
+                endSrc,
+                currentRegion.startDstLine,
+                endDst,
+                currentRegion.internalEdits);
+        currentRegion = null;
+        edits.add(edit);
+      }
+    }
+
+    private boolean shouldStartNewEdit(FixReplacement replacement) {
+      if (currentRegion == null) {
+        return true;
+      }
+      // New edit must be started if there is at least one unchanged line after the last edit
+      // Subtract 1 from replacement.range.startLine because it is a 1-based line number,
+      // and contentProcessor.srcPosition.line is a 0-based line number
+      return replacement.range.startLine - 1 > contentProcessor.srcPosition.line + 1;
+    }
+
+    private void processSrcContent(int toLine, int toColumn, boolean append)
+        throws IndexOutOfBoundsException {
+      // toLine >= currentSrcLineIndex
+      if (toLine == contentProcessor.srcPosition.line) {
+        contentProcessor.processLineToColumn(toColumn, append);
+      } else {
+        contentProcessor.processToEndOfLine(append);
+        contentProcessor.processMultiline(toLine, append);
+        contentProcessor.processLineToColumn(toColumn, append);
+      }
+    }
+
+    private void processReplacement(FixReplacement fix) {
+      if (currentRegion == null) {
+        currentRegion = new FixRegion();
+        currentRegion.startSrcLine = contentProcessor.srcPosition.line;
+        currentRegion.startSrcPos = contentProcessor.srcPosition.getLineStartPos();
+        currentRegion.startDstLine = contentProcessor.dstPosition.line;
+        currentRegion.startDstPos = contentProcessor.dstPosition.getLineStartPos();
+      }
+      int srcStartPos = contentProcessor.srcPosition.textPos;
+      int dstStartPos = contentProcessor.dstPosition.textPos;
+      contentProcessor.appendReplacement(fix.replacement);
+      processSrcContent(fix.range.endLine - 1, fix.range.endChar, false);
+
+      currentRegion.internalEdits.add(
+          new Edit(
+              srcStartPos - currentRegion.startSrcPos,
+              contentProcessor.srcPosition.textPos - currentRegion.startSrcPos,
+              dstStartPos - currentRegion.startDstPos,
+              contentProcessor.dstPosition.textPos - currentRegion.startDstPos));
+    }
+  }
+
+  private static class ContentProcessor {
+    static class ContentPosition {
+      int line;
+      int column;
+      int textPos;
+
+      void appendMultilineContent(int lineCount, int charCount) {
+        line += lineCount;
+        column = 0;
+        textPos += charCount;
+      }
+
+      void appendLineEndedWithEOLMark(int charCount) {
+        textPos += charCount;
+        line++;
+        column = 0;
+      }
+
+      void appendStringWithoutEOLMark(int charCount) {
+        textPos += charCount;
+        column += charCount;
+      }
+
+      int getLineStartPos() {
+        return textPos - column;
+      }
+    }
+
+    private final StringBuilder sb;
+    final ContentPosition srcPosition;
+    final ContentPosition dstPosition;
+    String currentSrcLine;
+    Text src;
+    boolean endOfSource;
+
+    ContentProcessor(Text src) {
+      this.src = src;
+      sb = new StringBuilder(src.size());
+      srcPosition = new ContentPosition();
+      dstPosition = new ContentPosition();
+      endOfSource = src.size() == 0;
+    }
+
+    void processMultiline(int toLine, boolean append) {
+      if (endOfSource || toLine <= srcPosition.line) {
+        return;
+      }
+      int fromLine = srcPosition.line;
+      String lines = src.getString(fromLine, toLine, false);
+      int lineCount = toLine - fromLine;
+      int charCount = lines.length();
+      srcPosition.appendMultilineContent(lineCount, charCount);
+
+      if (append) {
+        sb.append(lines);
+        dstPosition.appendMultilineContent(lineCount, charCount);
+      }
+      currentSrcLine = null;
+      endOfSource = srcPosition.line >= src.size();
+    }
+
+    void processToEndOfLine(boolean append) {
+      if (endOfSource) {
+        return;
+      }
+      String srcLine = getCurrentSrcLine();
+      int from = srcPosition.column;
+      int charCount = srcLine.length() - from;
+      boolean lastLineNoEOLMark = srcPosition.line >= src.size() - 1 && src.isMissingNewlineAtEnd();
+      if (!lastLineNoEOLMark) {
+        srcPosition.appendLineEndedWithEOLMark(charCount);
+        endOfSource = srcPosition.line >= src.size();
+      } else {
+        srcPosition.appendStringWithoutEOLMark(charCount);
+        endOfSource = true;
+      }
+      if (append) {
+        sb.append(srcLine, from, srcLine.length());
+        if (!lastLineNoEOLMark) {
+          dstPosition.appendLineEndedWithEOLMark(charCount);
+        } else {
+          dstPosition.appendStringWithoutEOLMark(charCount);
+        }
+      }
+      currentSrcLine = null;
+    }
+
+    void processLineToColumn(int to, boolean append) throws IndexOutOfBoundsException {
+      if (to == 0) {
+        return;
+      }
+      String srcLine = getCurrentSrcLine();
+      if (to > srcLine.length()) {
+        throw new IndexOutOfBoundsException("Parameter to is out of string");
+      } else if (to == srcLine.length()) {
+        if (srcPosition.line < src.size() - 1 || !src.isMissingNewlineAtEnd()) {
+          throw new IndexOutOfBoundsException("The processLineToColumn shouldn't add end of line");
+        }
+      }
+      int from = srcPosition.column;
+      int charCount = to - from;
+      srcPosition.appendStringWithoutEOLMark(charCount);
+      if (append) {
+        sb.append(srcLine, from, to);
+        dstPosition.appendStringWithoutEOLMark(charCount);
+      }
+    }
+
+    void appendLinesToEndOfContent() {
+      processMultiline(src.size(), true);
+    }
+
+    void appendReplacement(String replacement) {
+      if (replacement.length() == 0) {
+        return;
+      }
+      sb.append(replacement);
+      int lastNewLinePos = -1;
+      int newLineMarkCount = 0;
+      while (true) {
+        int index = replacement.indexOf('\n', lastNewLinePos + 1);
+        if (index < 0) {
+          break;
+        }
+        lastNewLinePos = index;
+        newLineMarkCount++;
+      }
+      if (newLineMarkCount > 0) {
+        dstPosition.appendMultilineContent(newLineMarkCount, lastNewLinePos + 1);
+      }
+      dstPosition.appendStringWithoutEOLMark(replacement.length() - lastNewLinePos - 1);
+    }
+
+    boolean hasMoreLines() {
+      return !endOfSource;
+    }
+
+    private String getCurrentSrcLine() {
+      if (currentSrcLine == null) {
+        currentSrcLine = src.getString(srcPosition.line, srcPosition.line + 1, false);
+      }
+      return currentSrcLine;
+    }
+  }
+
+  /** The result of applying fix to a file content */
+  public static class FixResult {
+    /** List of edits to transform an original text to a final text (with all fixes applied) */
+    public final ImmutableList<Edit> edits;
+    /** Final text with all applied fixes */
+    public final Text text;
+
+    FixResult(ImmutableList<Edit> edits, Text text) {
+      this.edits = edits;
+      this.text = text;
+    }
+  }
+}
diff --git a/java/com/google/gerrit/server/fixes/FixReplacementInterpreter.java b/java/com/google/gerrit/server/fixes/FixReplacementInterpreter.java
index 9d6df7d..32c2450 100644
--- a/java/com/google/gerrit/server/fixes/FixReplacementInterpreter.java
+++ b/java/com/google/gerrit/server/fixes/FixReplacementInterpreter.java
@@ -18,7 +18,6 @@
 import static java.util.stream.Collectors.groupingBy;
 
 import com.google.gerrit.common.RawInputUtil;
-import com.google.gerrit.entities.Comment;
 import com.google.gerrit.entities.FixReplacement;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.BinaryResult;
@@ -96,7 +95,8 @@
       throws BadRequestException, ResourceNotFoundException, IOException,
           ResourceConflictException {
     String fileContent = getFileContent(repository, projectState, patchSetCommitId, filePath);
-    String newFileContent = getNewFileContent(fileContent, fixReplacements);
+    String newFileContent = FixCalculator.getNewFileContent(fileContent, fixReplacements);
+
     return new ChangeFileContentModification(filePath, RawInputUtil.create(newFileContent));
   }
 
@@ -108,48 +108,4 @@
       return fileContent.asString();
     }
   }
-
-  private static String getNewFileContent(String fileContent, List<FixReplacement> fixReplacements)
-      throws ResourceConflictException {
-    List<FixReplacement> sortedReplacements = new ArrayList<>(fixReplacements);
-    sortedReplacements.sort(ASC_RANGE_FIX_REPLACEMENT_COMPARATOR);
-
-    LineIdentifier lineIdentifier = new LineIdentifier(fileContent);
-    StringModifier fileContentModifier = new StringModifier(fileContent);
-    for (FixReplacement fixReplacement : sortedReplacements) {
-      Comment.Range range = fixReplacement.range;
-      try {
-        int startLineIndex = lineIdentifier.getStartIndexOfLine(range.startLine);
-        int startLineLength = lineIdentifier.getLengthOfLine(range.startLine);
-
-        int endLineIndex = lineIdentifier.getStartIndexOfLine(range.endLine);
-        int endLineLength = lineIdentifier.getLengthOfLine(range.endLine);
-
-        if (range.startChar > startLineLength || range.endChar > endLineLength) {
-          throw new ResourceConflictException(
-              String.format(
-                  "Range %s refers to a non-existent offset (start line length: %s,"
-                      + " end line length: %s)",
-                  toString(range), startLineLength, endLineLength));
-        }
-
-        int startIndex = startLineIndex + range.startChar;
-        int endIndex = endLineIndex + range.endChar;
-        fileContentModifier.replace(startIndex, endIndex, fixReplacement.replacement);
-      } catch (StringIndexOutOfBoundsException e) {
-        // Most of the StringIndexOutOfBoundsException should never occur because we reject fix
-        // replacements for invalid ranges. However, we can't cover all cases for efficiency
-        // reasons. For instance, we don't determine the number of lines in a file. That's why we
-        // need to map this exception and thus provide a meaningful error.
-        throw new ResourceConflictException(
-            String.format("Cannot apply fix replacement for range %s", toString(range)), e);
-      }
-    }
-    return fileContentModifier.getResult();
-  }
-
-  private static String toString(Comment.Range range) {
-    return String.format(
-        "(%s:%s - %s:%s)", range.startLine, range.startChar, range.endLine, range.endChar);
-  }
 }
diff --git a/java/com/google/gerrit/server/fixes/LineIdentifier.java b/java/com/google/gerrit/server/fixes/LineIdentifier.java
deleted file mode 100644
index 3d09c34..0000000
--- a/java/com/google/gerrit/server/fixes/LineIdentifier.java
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright (C) 2017 The Android Open Source 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 com.google.gerrit.server.fixes;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * An identifier of lines in a string. Lines are sequences of characters which are separated by any
- * Unicode linebreak sequence as defined by the regular expression {@code \R}. If data for several
- * lines is requested, calls which are ordered according to ascending line numbers are the most
- * efficient.
- */
-class LineIdentifier {
-
-  private static final Pattern LINE_SEPARATOR_PATTERN = Pattern.compile("\\R");
-  private final Matcher lineSeparatorMatcher;
-
-  private int nextLineNumber;
-  private int nextLineStartIndex;
-  private int currentLineStartIndex;
-  private int currentLineEndIndex;
-
-  LineIdentifier(String string) {
-    requireNonNull(string);
-    lineSeparatorMatcher = LINE_SEPARATOR_PATTERN.matcher(string);
-    reset();
-  }
-
-  /**
-   * Returns the start index of the indicated line within the given string. Start indices are
-   * zero-based while line numbers are one-based.
-   *
-   * <p><b>Note:</b> Requesting data for several lines is more efficient if those calls occur with
-   * increasing line number.
-   *
-   * @param lineNumber the line whose start index should be determined
-   * @return the start index of the line
-   * @throws StringIndexOutOfBoundsException if the line number is negative, zero or greater than
-   *     the identified number of lines
-   */
-  public int getStartIndexOfLine(int lineNumber) {
-    findLine(lineNumber);
-    return currentLineStartIndex;
-  }
-
-  /**
-   * Returns the length of the indicated line in the given string. The character(s) used to separate
-   * lines aren't included in the count. Line numbers are one-based.
-   *
-   * <p><b>Note:</b> Requesting data for several lines is more efficient if those calls occur with
-   * increasing line number.
-   *
-   * @param lineNumber the line whose length should be determined
-   * @return the length of the line
-   * @throws StringIndexOutOfBoundsException if the line number is negative, zero or greater than
-   *     the identified number of lines
-   */
-  public int getLengthOfLine(int lineNumber) {
-    findLine(lineNumber);
-    return currentLineEndIndex - currentLineStartIndex;
-  }
-
-  private void findLine(int targetLineNumber) {
-    if (targetLineNumber <= 0) {
-      throw new StringIndexOutOfBoundsException("Line number must be positive");
-    }
-    if (targetLineNumber < nextLineNumber) {
-      reset();
-    }
-    while (nextLineNumber < targetLineNumber + 1 && lineSeparatorMatcher.find()) {
-      currentLineStartIndex = nextLineStartIndex;
-      currentLineEndIndex = lineSeparatorMatcher.start();
-      nextLineStartIndex = lineSeparatorMatcher.end();
-      nextLineNumber++;
-    }
-
-    // End of string
-    if (nextLineNumber == targetLineNumber) {
-      currentLineStartIndex = nextLineStartIndex;
-      currentLineEndIndex = lineSeparatorMatcher.regionEnd();
-    }
-    if (nextLineNumber < targetLineNumber) {
-      throw new StringIndexOutOfBoundsException(
-          String.format("Line %d isn't available", targetLineNumber));
-    }
-  }
-
-  private void reset() {
-    nextLineNumber = 1;
-    nextLineStartIndex = 0;
-    currentLineStartIndex = 0;
-    currentLineEndIndex = 0;
-    lineSeparatorMatcher.reset();
-  }
-}
diff --git a/java/com/google/gerrit/server/fixes/testing/BUILD b/java/com/google/gerrit/server/fixes/testing/BUILD
new file mode 100644
index 0000000..cccf7a5
--- /dev/null
+++ b/java/com/google/gerrit/server/fixes/testing/BUILD
@@ -0,0 +1,19 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+    name = "testing",
+    testonly = True,
+    srcs = glob(["*.java"]),
+    deps = [
+        "//java/com/google/gerrit/common:server",
+        "//java/com/google/gerrit/entities",
+        "//java/com/google/gerrit/jgit",
+        "//java/com/google/gerrit/server",
+        "//java/com/google/gerrit/truth",
+        "//lib:guava",
+        "//lib:jgit",
+        "//lib/truth",
+    ],
+)
diff --git a/java/com/google/gerrit/server/fixes/testing/FixResultSubject.java b/java/com/google/gerrit/server/fixes/testing/FixResultSubject.java
new file mode 100644
index 0000000..21f96cd
--- /dev/null
+++ b/java/com/google/gerrit/server/fixes/testing/FixResultSubject.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2019 The Android Open Source 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 com.google.gerrit.server.fixes.testing;
+
+import static com.google.common.truth.Truth.assertAbout;
+import static com.google.gerrit.server.fixes.testing.GitEditSubject.gitEdits;
+import static com.google.gerrit.truth.ListSubject.elements;
+
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.StringSubject;
+import com.google.common.truth.Subject;
+import com.google.gerrit.server.fixes.FixCalculator.FixResult;
+import com.google.gerrit.truth.ListSubject;
+import org.eclipse.jgit.diff.Edit;
+
+public class FixResultSubject extends Subject {
+  public static FixResultSubject assertThat(FixResult fixResult) {
+    return assertAbout(FixResultSubject::new).that(fixResult);
+  }
+
+  private final FixResult fixResult;
+
+  private FixResultSubject(FailureMetadata failureMetadata, FixResult fixResult) {
+    super(failureMetadata, fixResult);
+    this.fixResult = fixResult;
+  }
+
+  public StringSubject text() {
+    isNotNull();
+    return check("text").that(fixResult.text.getString(0, fixResult.text.size(), false));
+  }
+
+  public ListSubject<GitEditSubject, Edit> edits() {
+    isNotNull();
+    return check("edits").about(elements()).thatCustom(fixResult.edits, gitEdits());
+  }
+}
diff --git a/java/com/google/gerrit/server/fixes/testing/GitEditSubject.java b/java/com/google/gerrit/server/fixes/testing/GitEditSubject.java
new file mode 100644
index 0000000..53b88b1
--- /dev/null
+++ b/java/com/google/gerrit/server/fixes/testing/GitEditSubject.java
@@ -0,0 +1,87 @@
+// Copyright (C) 2019 The Android Open Source 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 com.google.gerrit.server.fixes.testing;
+
+import static com.google.common.truth.Truth.assertAbout;
+import static com.google.gerrit.truth.ListSubject.elements;
+
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.gerrit.jgit.diff.ReplaceEdit;
+import com.google.gerrit.truth.ListSubject;
+import org.eclipse.jgit.diff.Edit;
+import org.eclipse.jgit.diff.Edit.Type;
+
+public class GitEditSubject extends Subject {
+
+  public static GitEditSubject assertThat(Edit edit) {
+    return assertAbout(gitEdits()).that(edit);
+  }
+
+  public static Subject.Factory<GitEditSubject, Edit> gitEdits() {
+    return GitEditSubject::new;
+  }
+
+  private final Edit edit;
+
+  private GitEditSubject(FailureMetadata failureMetadata, Edit edit) {
+    super(failureMetadata, edit);
+    this.edit = edit;
+  }
+
+  public void hasRegions(int beginA, int endA, int beginB, int endB) {
+    isNotNull();
+    check("beginA").that(edit.getBeginA()).isEqualTo(beginA);
+    check("endA").that(edit.getEndA()).isEqualTo(endA);
+    check("beginB").that(edit.getBeginB()).isEqualTo(beginB);
+    check("endB").that(edit.getEndB()).isEqualTo(endB);
+  }
+
+  public void hasType(Type type) {
+    isNotNull();
+    check("getType").that(edit.getType()).isEqualTo(type);
+  }
+
+  public void isInsert(int insertPos, int beginB, int insertedLength) {
+    isNotNull();
+    hasType(Type.INSERT);
+    hasRegions(insertPos, insertPos, beginB, beginB + insertedLength);
+  }
+
+  public void isDelete(int deletePos, int deletedLength, int posB) {
+    isNotNull();
+    hasType(Type.DELETE);
+    hasRegions(deletePos, deletePos + deletedLength, posB, posB);
+  }
+
+  public void isReplace(int originalPos, int originalLength, int newPos, int newLength) {
+    isNotNull();
+    hasType(Type.REPLACE);
+    hasRegions(originalPos, originalPos + originalLength, newPos, newPos + newLength);
+  }
+
+  public void isEmpty() {
+    isNotNull();
+    hasType(Type.EMPTY);
+  }
+
+  public ListSubject<GitEditSubject, Edit> internalEdits() {
+    isNotNull();
+    isInstanceOf(ReplaceEdit.class);
+    return check("internalEdits")
+        .about(elements())
+        .thatCustom(((ReplaceEdit) edit).getInternalEdits(), gitEdits());
+  }
+}
diff --git a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
index b5842a9..92df794 100644
--- a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
+++ b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
@@ -22,9 +22,13 @@
 import com.google.gerrit.common.data.PatchScript;
 import com.google.gerrit.common.data.PatchScript.DisplayMethod;
 import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.FixReplacement;
 import com.google.gerrit.entities.Patch;
 import com.google.gerrit.entities.Patch.ChangeType;
+import com.google.gerrit.entities.Patch.PatchType;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.server.fixes.FixCalculator;
 import com.google.gerrit.server.mime.FileTypeRegistry;
 import com.google.gerrit.server.patch.DiffContentCalculator.DiffCalculatorResult;
 import com.google.gerrit.server.patch.DiffContentCalculator.TextSource;
@@ -32,6 +36,7 @@
 import eu.medsea.mimeutil.MimeType;
 import eu.medsea.mimeutil.MimeUtil2;
 import java.io.IOException;
+import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
@@ -90,9 +95,7 @@
     ResolvedSides sides =
         resolveSides(
             git, sidesResolver, oldName(change), newName(change), list.getOldId(), list.getNewId());
-    PatchSide a = sides.a;
-    PatchSide b = sides.b;
-    return build(a, b, change, comments, history);
+    return build(sides.a, sides.b, change, comments, history);
   }
 
   private ResolvedSides resolveSides(
@@ -111,6 +114,45 @@
     }
   }
 
+  PatchScript toPatchScript(
+      Repository git, ObjectId baseId, String fileName, List<FixReplacement> fixReplacements)
+      throws IOException, ResourceConflictException {
+    SidesResolver sidesResolver = new SidesResolver(git, ComparisonType.againstOtherPatchSet());
+    PatchSide a = resolveSideA(git, sidesResolver, fileName, baseId);
+    FixCalculator.FixResult fixResult = FixCalculator.calculateFix(a.src, fixReplacements);
+    PatchSide b =
+        new PatchSide(
+            null,
+            fileName,
+            ObjectId.zeroId(),
+            a.mode,
+            fixResult.text.getContent(),
+            fixResult.text,
+            a.mimeType,
+            a.displayMethod,
+            a.fileMode);
+
+    PatchFileChange change =
+        new PatchFileChange(
+            fixResult.edits,
+            ImmutableSet.of(),
+            ImmutableList.of(),
+            fileName,
+            fileName,
+            ChangeType.MODIFIED,
+            PatchType.UNIFIED);
+
+    return build(a, b, change, null, null);
+  }
+
+  private PatchSide resolveSideA(
+      Repository git, SidesResolver sidesResolver, String path, ObjectId baseId)
+      throws IOException {
+    try (ObjectReader reader = git.newObjectReader()) {
+      return sidesResolver.resolve(registry, reader, path, null, baseId, true);
+    }
+  }
+
   private PatchScript build(
       PatchSide a,
       PatchSide b,
diff --git a/java/com/google/gerrit/server/patch/PatchScriptFactoryForAutoFix.java b/java/com/google/gerrit/server/patch/PatchScriptFactoryForAutoFix.java
new file mode 100644
index 0000000..cf07190
--- /dev/null
+++ b/java/com/google/gerrit/server/patch/PatchScriptFactoryForAutoFix.java
@@ -0,0 +1,134 @@
+// Copyright (C) 2019 The Android Open Source 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 com.google.gerrit.server.patch;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.data.PatchScript;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.FixReplacement;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.extensions.client.DiffPreferencesInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.server.git.LargeObjectException;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.permissions.ChangePermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.inject.Provider;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+import java.io.IOException;
+import java.util.concurrent.Callable;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+
+public class PatchScriptFactoryForAutoFix implements Callable<PatchScript> {
+
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+  public interface Factory {
+
+    PatchScriptFactoryForAutoFix create(
+        Repository git,
+        ChangeNotes notes,
+        String fileName,
+        PatchSet patchSet,
+        ImmutableList<FixReplacement> fixReplacements,
+        DiffPreferencesInfo diffPrefs);
+  }
+
+  private final PermissionBackend permissionBackend;
+  private final ProjectCache projectCache;
+  private final Change.Id changeId;
+  private final ChangeNotes notes;
+  private final Provider<PatchScriptBuilder> builderFactory;
+  private final Repository git;
+  private final PatchSet patchSet;
+  private final String fileName;
+  private final DiffPreferencesInfo diffPrefs;
+  private final ImmutableList<FixReplacement> fixReplacements;
+
+  @AssistedInject
+  PatchScriptFactoryForAutoFix(
+      Provider<PatchScriptBuilder> builderFactory,
+      PermissionBackend permissionBackend,
+      ProjectCache projectCache,
+      @Assisted Repository git,
+      @Assisted ChangeNotes notes,
+      @Assisted String fileName,
+      @Assisted PatchSet patchSet,
+      @Assisted ImmutableList<FixReplacement> fixReplacements,
+      @Assisted DiffPreferencesInfo diffPrefs) {
+    this.notes = notes;
+    this.permissionBackend = permissionBackend;
+    this.projectCache = projectCache;
+    this.changeId = patchSet.id().changeId();
+    this.git = git;
+    this.patchSet = patchSet;
+    this.fileName = fileName;
+    this.fixReplacements = fixReplacements;
+    this.builderFactory = builderFactory;
+    this.diffPrefs = diffPrefs;
+  }
+
+  @Override
+  public PatchScript call()
+      throws LargeObjectException, AuthException, InvalidChangeOperationException, IOException,
+          PermissionBackendException {
+
+    try {
+      permissionBackend.currentUser().change(notes).check(ChangePermission.READ);
+    } catch (AuthException e) {
+      throw new NoSuchChangeException(changeId);
+    }
+
+    if (!projectCache.checkedGet(notes.getProjectName()).statePermitsRead()) {
+      throw new NoSuchChangeException(changeId);
+    }
+
+    return createPatchScript();
+  }
+
+  private PatchScript createPatchScript() throws LargeObjectException {
+    checkState(patchSet.id().get() != 0, "edit not supported for left side");
+    PatchScriptBuilder b = newBuilder();
+    try {
+      ObjectId baseId = patchSet.commitId();
+      return b.toPatchScript(git, baseId, fileName, fixReplacements);
+    } catch (ResourceConflictException e) {
+      logger.atSevere().withCause(e).log("AutoFix replacements is not valid");
+      throw new IllegalStateException("AutoFix replacements is not valid", e);
+    } catch (IOException e) {
+      logger.atSevere().withCause(e).log("File content unavailable");
+      throw new NoSuchChangeException(notes.getChangeId(), e);
+    } catch (org.eclipse.jgit.errors.LargeObjectException err) {
+      throw new LargeObjectException("File content is too large", err);
+    }
+  }
+
+  private PatchScriptBuilder newBuilder() {
+    PatchScriptBuilder b = builderFactory.get();
+    b.setChange(notes.getChange());
+    b.setDiffPrefs(diffPrefs);
+    return b;
+  }
+}
diff --git a/java/com/google/gerrit/server/restapi/change/GetFixPreview.java b/java/com/google/gerrit/server/restapi/change/GetFixPreview.java
index db89819..0666756 100644
--- a/java/com/google/gerrit/server/restapi/change/GetFixPreview.java
+++ b/java/com/google/gerrit/server/restapi/change/GetFixPreview.java
@@ -14,20 +14,129 @@
 
 package com.google.gerrit.server.restapi.change;
 
+import static java.util.stream.Collectors.groupingBy;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.data.PatchScript;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.FixReplacement;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 import com.google.gerrit.extensions.common.DiffInfo;
+import com.google.gerrit.extensions.common.DiffWebLinkInfo;
+import com.google.gerrit.extensions.common.WebLinkInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.server.change.FixResource;
+import com.google.gerrit.server.diff.DiffInfoCreator;
+import com.google.gerrit.server.diff.DiffSide;
+import com.google.gerrit.server.diff.DiffSide.Type;
+import com.google.gerrit.server.diff.DiffWebLinksProvider;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.LargeObjectException;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.patch.PatchScriptFactoryForAutoFix;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import java.io.IOException;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import org.eclipse.jgit.lib.Repository;
 
 @Singleton
 public class GetFixPreview implements RestReadView<FixResource> {
 
+  private final ProjectCache projectCache;
+  private final GitRepositoryManager repoManager;
+  private final PatchScriptFactoryForAutoFix.Factory patchScriptFactoryFactory;
+
+  @Inject
+  GetFixPreview(
+      ProjectCache projectCache,
+      GitRepositoryManager repoManager,
+      PatchScriptFactoryForAutoFix.Factory patchScriptFactoryFactory) {
+    this.projectCache = projectCache;
+    this.repoManager = repoManager;
+    this.patchScriptFactoryFactory = patchScriptFactoryFactory;
+  }
+
   @Override
-  public Response<Map<String, DiffInfo>> apply(FixResource resource) {
+  public Response<Map<String, DiffInfo>> apply(FixResource resource)
+      throws PermissionBackendException, ResourceNotFoundException, ResourceConflictException,
+          AuthException, IOException, InvalidChangeOperationException {
     Map<String, DiffInfo> result = new HashMap<>();
+    PatchSet patchSet = resource.getRevisionResource().getPatchSet();
+    ChangeNotes notes = resource.getRevisionResource().getNotes();
+    Change change = notes.getChange();
+    ProjectState state = projectCache.get(change.getProject());
+    Map<String, List<FixReplacement>> fixReplacementsPerFilePath =
+        resource.getFixReplacements().stream()
+            .collect(groupingBy(fixReplacement -> fixReplacement.path));
+    try {
+      try (Repository git = repoManager.openRepository(notes.getProjectName())) {
+        for (Map.Entry<String, List<FixReplacement>> entry :
+            fixReplacementsPerFilePath.entrySet()) {
+          String fileName = entry.getKey();
+          DiffInfo diffInfo =
+              getFixPreviewForSingleFile(
+                  git, patchSet, state, notes, fileName, ImmutableList.copyOf(entry.getValue()));
+          result.put(fileName, diffInfo);
+        }
+      }
+    } catch (NoSuchChangeException e) {
+      throw new ResourceNotFoundException(e.getMessage(), e);
+    } catch (LargeObjectException e) {
+      throw new ResourceConflictException(e.getMessage(), e);
+    }
     return Response.ok(result);
   }
+
+  private DiffInfo getFixPreviewForSingleFile(
+      Repository git,
+      PatchSet patchSet,
+      ProjectState state,
+      ChangeNotes notes,
+      String fileName,
+      ImmutableList<FixReplacement> fixReplacements)
+      throws PermissionBackendException, AuthException, LargeObjectException,
+          InvalidChangeOperationException, IOException {
+    PatchScriptFactoryForAutoFix psf =
+        patchScriptFactoryFactory.create(
+            git, notes, fileName, patchSet, fixReplacements, DiffPreferencesInfo.defaults());
+    PatchScript ps = psf.call();
+
+    DiffSide sideA =
+        DiffSide.create(
+            ps.getFileInfoA(),
+            MoreObjects.firstNonNull(ps.getOldName(), ps.getNewName()),
+            Type.SIDE_A);
+    DiffSide sideB = DiffSide.create(ps.getFileInfoB(), ps.getNewName(), DiffSide.Type.SIDE_B);
+
+    DiffInfoCreator diffInfoCreator =
+        new DiffInfoCreator(state, new DiffWebLinksProviderImpl(), true);
+    return diffInfoCreator.create(ps, sideA, sideB);
+  }
+
+  private static class DiffWebLinksProviderImpl implements DiffWebLinksProvider {
+
+    @Override
+    public ImmutableList<DiffWebLinkInfo> getDiffLinks() {
+      return ImmutableList.of();
+    }
+
+    @Override
+    public ImmutableList<WebLinkInfo> getFileWebLinks(Type fileInfoType) {
+      return ImmutableList.of();
+    }
+  }
 }
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
index db1e99b7..abf0279 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RobotCommentsIT.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT;
+import static com.google.gerrit.extensions.common.testing.DiffInfoSubject.assertThat;
 import static com.google.gerrit.extensions.common.testing.EditInfoSubject.assertThat;
 import static com.google.gerrit.extensions.common.testing.RobotCommentInfoSubject.assertThatList;
 import static com.google.gerrit.testing.GerritJUnit.assertThrows;
@@ -31,7 +32,9 @@
 import com.google.gerrit.extensions.api.changes.ReviewInput.RobotCommentInput;
 import com.google.gerrit.extensions.client.Comment;
 import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ChangeType;
 import com.google.gerrit.extensions.common.DiffInfo;
+import com.google.gerrit.extensions.common.DiffInfo.IntraLineStatus;
 import com.google.gerrit.extensions.common.EditInfo;
 import com.google.gerrit.extensions.common.FixReplacementInfo;
 import com.google.gerrit.extensions.common.FixSuggestionInfo;
@@ -50,9 +53,12 @@
 import java.util.Objects;
 import java.util.Optional;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
 public class RobotCommentsIT extends AbstractDaemonTest {
+  private static final String PLAIN_TEXT_CONTENT_TYPE = "text/plain";
+
   private static final String FILE_NAME = "file_to_fix.txt";
   private static final String FILE_NAME2 = "another_file_to_fix.txt";
   private static final String FILE_CONTENT =
@@ -61,6 +67,7 @@
   private static final String FILE_CONTENT2 = "1st line\n2nd line\n3rd line\n";
 
   private String changeId;
+  private String commitId;
   private FixReplacementInfo fixReplacementInfo;
   private FixSuggestionInfo fixSuggestionInfo;
   private RobotCommentInput withFixRobotCommentInput;
@@ -75,6 +82,7 @@
             ImmutableMap.of(FILE_NAME, FILE_CONTENT, FILE_NAME2, FILE_CONTENT2));
     PushOneCommit.Result changeResult = push.to("refs/for/master");
     changeId = changeResult.getChangeId();
+    commitId = changeResult.getCommit().getName();
 
     fixReplacementInfo = createFixReplacementInfo();
     fixSuggestionInfo = createFixSuggestionInfo(fixReplacementInfo);
@@ -971,15 +979,130 @@
   }
 
   @Test
+  @Ignore
+  public void getFixPreviewForNonExistingFile() throws Exception {
+    // Not implemented yet.
+    fixReplacementInfo.path = "a_non_existent_file.txt";
+    fixReplacementInfo.range = createRange(1, 0, 2, 0);
+    fixReplacementInfo.replacement = "Modified content\n";
+
+    addRobotComment(changeId, withFixRobotCommentInput);
+    List<RobotCommentInfo> robotCommentInfos = getRobotComments();
+    List<String> fixIds = getFixIds(robotCommentInfos);
+    String fixId = Iterables.getOnlyElement(fixIds);
+
+    assertThrows(
+        BadRequestException.class,
+        () -> gApi.changes().id(changeId).current().getFixPreview(fixId));
+  }
+
+  @Test
   public void getFixPreview() throws Exception {
+    FixReplacementInfo fixReplacementInfoFile1 = new FixReplacementInfo();
+    fixReplacementInfoFile1.path = FILE_NAME;
+    fixReplacementInfoFile1.replacement = "some replacement code";
+    fixReplacementInfoFile1.range = createRange(3, 9, 8, 4);
+
+    FixReplacementInfo fixReplacementInfoFile2 = new FixReplacementInfo();
+    fixReplacementInfoFile2.path = FILE_NAME2;
+    fixReplacementInfoFile2.replacement = "New line\n";
+    fixReplacementInfoFile2.range = createRange(2, 0, 2, 0);
+
+    fixSuggestionInfo = createFixSuggestionInfo(fixReplacementInfoFile1, fixReplacementInfoFile2);
+
+    withFixRobotCommentInput = createRobotCommentInput(fixSuggestionInfo);
+
     addRobotComment(changeId, withFixRobotCommentInput);
     List<RobotCommentInfo> robotCommentInfos = getRobotComments();
 
     List<String> fixIds = getFixIds(robotCommentInfos);
     String fixId = Iterables.getOnlyElement(fixIds);
 
-    Map<String, DiffInfo> result = gApi.changes().id(changeId).current().getFixPreview(fixId);
-    assertThat(result).isEmpty();
+    Map<String, DiffInfo> fixPreview = gApi.changes().id(changeId).current().getFixPreview(fixId);
+    assertThat(fixPreview).hasSize(2);
+    assertThat(fixPreview).containsKey(FILE_NAME);
+    assertThat(fixPreview).containsKey(FILE_NAME2);
+
+    DiffInfo diff = fixPreview.get(FILE_NAME);
+    assertThat(diff).intralineStatus().isEqualTo(IntraLineStatus.OK);
+    assertThat(diff).webLinks().isNull();
+    assertThat(diff).binary().isNull();
+    assertThat(diff).diffHeader().isNull();
+    assertThat(diff).changeType().isEqualTo(ChangeType.MODIFIED);
+    assertThat(diff).metaA().totalLineCount().isEqualTo(11);
+    assertThat(diff).metaA().name().isEqualTo(FILE_NAME);
+    assertThat(diff).metaA().commitId().isEqualTo(commitId);
+    assertThat(diff).metaA().contentType().isEqualTo(PLAIN_TEXT_CONTENT_TYPE);
+    assertThat(diff).metaA().webLinks().isNull();
+    assertThat(diff).metaB().totalLineCount().isEqualTo(6);
+    assertThat(diff).metaB().name().isEqualTo(FILE_NAME);
+    assertThat(diff).metaB().commitId().isNull();
+    assertThat(diff).metaB().contentType().isEqualTo(PLAIN_TEXT_CONTENT_TYPE);
+    assertThat(diff).metaB().webLinks().isNull();
+
+    assertThat(diff).content().hasSize(3);
+    assertThat(diff)
+        .content()
+        .element(0)
+        .commonLines()
+        .containsExactly("First line", "Second line");
+    assertThat(diff).content().element(0).linesOfA().isNull();
+    assertThat(diff).content().element(0).linesOfB().isNull();
+
+    assertThat(diff).content().element(1).commonLines().isNull();
+    assertThat(diff)
+        .content()
+        .element(1)
+        .linesOfA()
+        .containsExactly(
+            "Third line", "Fourth line", "Fifth line", "Sixth line", "Seventh line", "Eighth line");
+    assertThat(diff)
+        .content()
+        .element(1)
+        .linesOfB()
+        .containsExactly("Third linsome replacement codeth line");
+
+    assertThat(diff)
+        .content()
+        .element(2)
+        .commonLines()
+        .containsExactly("Ninth line", "Tenth line", "");
+    assertThat(diff).content().element(2).linesOfA().isNull();
+    assertThat(diff).content().element(2).linesOfB().isNull();
+
+    DiffInfo diff2 = fixPreview.get(FILE_NAME2);
+    assertThat(diff2).intralineStatus().isEqualTo(IntraLineStatus.OK);
+    assertThat(diff2).webLinks().isNull();
+    assertThat(diff2).binary().isNull();
+    assertThat(diff2).diffHeader().isNull();
+    assertThat(diff2).changeType().isEqualTo(ChangeType.MODIFIED);
+    assertThat(diff2).metaA().totalLineCount().isEqualTo(4);
+    assertThat(diff2).metaA().name().isEqualTo(FILE_NAME2);
+    assertThat(diff2).metaA().commitId().isEqualTo(commitId);
+    assertThat(diff2).metaA().contentType().isEqualTo(PLAIN_TEXT_CONTENT_TYPE);
+    assertThat(diff2).metaA().webLinks().isNull();
+    assertThat(diff2).metaB().totalLineCount().isEqualTo(5);
+    assertThat(diff2).metaB().name().isEqualTo(FILE_NAME2);
+    assertThat(diff2).metaB().commitId().isNull();
+    assertThat(diff2).metaA().contentType().isEqualTo(PLAIN_TEXT_CONTENT_TYPE);
+    assertThat(diff2).metaB().webLinks().isNull();
+
+    assertThat(diff2).content().hasSize(3);
+    assertThat(diff2).content().element(0).commonLines().containsExactly("1st line");
+    assertThat(diff2).content().element(0).linesOfA().isNull();
+    assertThat(diff2).content().element(0).linesOfB().isNull();
+
+    assertThat(diff2).content().element(1).commonLines().isNull();
+    assertThat(diff2).content().element(1).linesOfA().isNull();
+    assertThat(diff2).content().element(1).linesOfB().containsExactly("New line");
+
+    assertThat(diff2)
+        .content()
+        .element(2)
+        .commonLines()
+        .containsExactly("2nd line", "3rd line", "");
+    assertThat(diff2).content().element(2).linesOfA().isNull();
+    assertThat(diff2).content().element(2).linesOfB().isNull();
   }
 
   private static RobotCommentInput createRobotCommentInputWithMandatoryFields() {
diff --git a/javatests/com/google/gerrit/server/BUILD b/javatests/com/google/gerrit/server/BUILD
index 59ed018..022813b 100644
--- a/javatests/com/google/gerrit/server/BUILD
+++ b/javatests/com/google/gerrit/server/BUILD
@@ -55,6 +55,7 @@
         "//java/com/google/gerrit/server/account/externalids/testing",
         "//java/com/google/gerrit/server/cache/serialize",
         "//java/com/google/gerrit/server/cache/testing",
+        "//java/com/google/gerrit/server/fixes/testing",
         "//java/com/google/gerrit/server/git/receive:ref_cache",
         "//java/com/google/gerrit/server/ioutil",
         "//java/com/google/gerrit/server/logging",
diff --git a/javatests/com/google/gerrit/server/fixes/LineIdentifierTest.java b/javatests/com/google/gerrit/server/fixes/LineIdentifierTest.java
deleted file mode 100644
index ba80c02..0000000
--- a/javatests/com/google/gerrit/server/fixes/LineIdentifierTest.java
+++ /dev/null
@@ -1,255 +0,0 @@
-// Copyright (C) 2017 The Android Open Source 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 com.google.gerrit.server.fixes;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.testing.GerritJUnit.assertThrows;
-
-import org.junit.Test;
-
-public class LineIdentifierTest {
-  @Test
-  public void lineNumberMustBePositive() {
-    LineIdentifier lineIdentifier = new LineIdentifier("First line\nSecond line");
-    StringIndexOutOfBoundsException thrown =
-        assertThrows(
-            StringIndexOutOfBoundsException.class, () -> lineIdentifier.getStartIndexOfLine(0));
-    assertThat(thrown).hasMessageThat().contains("positive");
-  }
-
-  @Test
-  public void lineNumberMustIndicateAnAvailableLine() {
-    LineIdentifier lineIdentifier = new LineIdentifier("First line\nSecond line");
-    StringIndexOutOfBoundsException thrown =
-        assertThrows(
-            StringIndexOutOfBoundsException.class, () -> lineIdentifier.getStartIndexOfLine(3));
-    assertThat(thrown).hasMessageThat().contains("Line 3 isn't available");
-  }
-
-  @Test
-  public void startIndexOfFirstLineIsRecognized() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678\n123\n1234567");
-    int startIndex = lineIdentifier.getStartIndexOfLine(1);
-    assertThat(startIndex).isEqualTo(0);
-  }
-
-  @Test
-  public void lengthOfFirstLineIsCorrect() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678\n123\n1234567");
-    int lineLength = lineIdentifier.getLengthOfLine(1);
-    assertThat(lineLength).isEqualTo(8);
-  }
-
-  @Test
-  public void startIndexOfSecondLineIsRecognized() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678\n123\n1234567");
-    int startIndex = lineIdentifier.getStartIndexOfLine(2);
-    assertThat(startIndex).isEqualTo(9);
-  }
-
-  @Test
-  public void lengthOfSecondLineIsCorrect() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678\n123\n1234567");
-    int lineLength = lineIdentifier.getLengthOfLine(2);
-    assertThat(lineLength).isEqualTo(3);
-  }
-
-  @Test
-  public void startIndexOfLastLineIsRecognized() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678\n123\n1234567");
-    int startIndex = lineIdentifier.getStartIndexOfLine(3);
-    assertThat(startIndex).isEqualTo(13);
-  }
-
-  @Test
-  public void lengthOfLastLineIsCorrect() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678\n123\n1234567");
-    int lineLength = lineIdentifier.getLengthOfLine(3);
-    assertThat(lineLength).isEqualTo(7);
-  }
-
-  @Test
-  public void emptyFirstLineIsRecognized() {
-    LineIdentifier lineIdentifier = new LineIdentifier("\n123\n1234567");
-    int startIndex = lineIdentifier.getStartIndexOfLine(1);
-    assertThat(startIndex).isEqualTo(0);
-  }
-
-  @Test
-  public void lengthOfEmptyFirstLineIsCorrect() {
-    LineIdentifier lineIdentifier = new LineIdentifier("\n123\n1234567");
-    int lineLength = lineIdentifier.getLengthOfLine(1);
-    assertThat(lineLength).isEqualTo(0);
-  }
-
-  @Test
-  public void emptyIntermediaryLineIsRecognized() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678\n\n1234567");
-    int startIndex = lineIdentifier.getStartIndexOfLine(2);
-    assertThat(startIndex).isEqualTo(9);
-  }
-
-  @Test
-  public void lengthOfEmptyIntermediaryLineIsCorrect() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678\n\n1234567");
-    int lineLength = lineIdentifier.getLengthOfLine(2);
-    assertThat(lineLength).isEqualTo(0);
-  }
-
-  @Test
-  public void lineAfterIntermediaryLineIsRecognized() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678\n\n1234567");
-    int startIndex = lineIdentifier.getStartIndexOfLine(3);
-    assertThat(startIndex).isEqualTo(10);
-  }
-
-  @Test
-  public void emptyLastLineIsRecognized() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678\n123\n");
-    int startIndex = lineIdentifier.getStartIndexOfLine(3);
-    assertThat(startIndex).isEqualTo(13);
-  }
-
-  @Test
-  public void lengthOfEmptyLastLineIsCorrect() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678\n123\n");
-    int lineLength = lineIdentifier.getLengthOfLine(3);
-    assertThat(lineLength).isEqualTo(0);
-  }
-
-  @Test
-  public void startIndexOfSingleLineIsRecognized() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678");
-    int startIndex = lineIdentifier.getStartIndexOfLine(1);
-    assertThat(startIndex).isEqualTo(0);
-  }
-
-  @Test
-  public void lengthOfSingleLineIsCorrect() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678");
-    int lineLength = lineIdentifier.getLengthOfLine(1);
-    assertThat(lineLength).isEqualTo(8);
-  }
-
-  @Test
-  public void startIndexOfSingleEmptyLineIsRecognized() {
-    LineIdentifier lineIdentifier = new LineIdentifier("");
-    int startIndex = lineIdentifier.getStartIndexOfLine(1);
-    assertThat(startIndex).isEqualTo(0);
-  }
-
-  @Test
-  public void lengthOfSingleEmptyLineIsCorrect() {
-    LineIdentifier lineIdentifier = new LineIdentifier("");
-    int lineLength = lineIdentifier.getLengthOfLine(1);
-    assertThat(lineLength).isEqualTo(0);
-  }
-
-  @Test
-  public void lookingUpSubsequentLinesIsPossible() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678\n123\n1234567\n12");
-
-    int firstLineStartIndex = lineIdentifier.getStartIndexOfLine(1);
-    assertThat(firstLineStartIndex).isEqualTo(0);
-
-    int secondLineStartIndex = lineIdentifier.getStartIndexOfLine(2);
-    assertThat(secondLineStartIndex).isEqualTo(9);
-  }
-
-  @Test
-  public void lookingUpNotSubsequentLinesInAscendingOrderIsPossible() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678\n123\n1234567\n12");
-
-    int firstLineStartIndex = lineIdentifier.getStartIndexOfLine(1);
-    assertThat(firstLineStartIndex).isEqualTo(0);
-
-    int fourthLineStartIndex = lineIdentifier.getStartIndexOfLine(4);
-    assertThat(fourthLineStartIndex).isEqualTo(21);
-  }
-
-  @Test
-  public void lookingUpNotSubsequentLinesInDescendingOrderIsPossible() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678\n123\n1234567\n12");
-
-    int fourthLineStartIndex = lineIdentifier.getStartIndexOfLine(4);
-    assertThat(fourthLineStartIndex).isEqualTo(21);
-
-    int secondLineStartIndex = lineIdentifier.getStartIndexOfLine(2);
-    assertThat(secondLineStartIndex).isEqualTo(9);
-  }
-
-  @Test
-  public void linesSeparatedByOnlyCarriageReturnAreRecognized() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678\r123\r12");
-    int startIndex = lineIdentifier.getStartIndexOfLine(2);
-    assertThat(startIndex).isEqualTo(9);
-  }
-
-  @Test
-  public void lengthOfLinesSeparatedByOnlyCarriageReturnIsCorrect() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678\r123\r12");
-    int lineLength = lineIdentifier.getLengthOfLine(2);
-    assertThat(lineLength).isEqualTo(3);
-  }
-
-  @Test
-  public void linesSeparatedByLineFeedAndCarriageReturnAreRecognized() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678\r\n123\r\n12");
-    int startIndex = lineIdentifier.getStartIndexOfLine(2);
-    assertThat(startIndex).isEqualTo(10);
-  }
-
-  @Test
-  public void lengthOfLinesSeparatedByLineFeedAndCarriageReturnIsCorrect() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678\r\n123\r\n12");
-    int lineLength = lineIdentifier.getLengthOfLine(2);
-    assertThat(lineLength).isEqualTo(3);
-  }
-
-  @Test
-  public void linesSeparatedByMixtureOfCarriageReturnAndLineFeedAreRecognized() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678\r123\r\n12\n123456\r\n1234");
-    int startIndex = lineIdentifier.getStartIndexOfLine(5);
-    assertThat(startIndex).isEqualTo(25);
-  }
-
-  @Test
-  public void linesSeparatedBySomeUnicodeLinebreakCharacterAreRecognized() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678\u2029123\u202912");
-    int startIndex = lineIdentifier.getStartIndexOfLine(2);
-    assertThat(startIndex).isEqualTo(9);
-  }
-
-  @Test
-  public void lengthOfLinesSeparatedBySomeUnicodeLinebreakCharacterIsCorrect() {
-    LineIdentifier lineIdentifier = new LineIdentifier("12345678\u2029123\u202912");
-    int lineLength = lineIdentifier.getLengthOfLine(2);
-    assertThat(lineLength).isEqualTo(3);
-  }
-
-  @Test
-  public void blanksAreNotInterpretedAsLineSeparators() {
-    LineIdentifier lineIdentifier = new LineIdentifier("1 2345678\n123\n12");
-    int startIndex = lineIdentifier.getStartIndexOfLine(2);
-    assertThat(startIndex).isEqualTo(10);
-  }
-
-  @Test
-  public void tabsAreNotInterpretedAsLineSeparators() {
-    LineIdentifier lineIdentifier = new LineIdentifier("123\t45678\n123\n12");
-    int startIndex = lineIdentifier.getStartIndexOfLine(2);
-    assertThat(startIndex).isEqualTo(10);
-  }
-}
diff --git a/javatests/com/google/gerrit/server/fixes/fixCalculator/EmptyContentTest.java b/javatests/com/google/gerrit/server/fixes/fixCalculator/EmptyContentTest.java
new file mode 100644
index 0000000..51fbc67
--- /dev/null
+++ b/javatests/com/google/gerrit/server/fixes/fixCalculator/EmptyContentTest.java
@@ -0,0 +1,68 @@
+// Copyright (C) 2019 The Android Open Source 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 com.google.gerrit.server.fixes.fixCalculator;
+
+import static com.google.gerrit.server.fixes.testing.FixResultSubject.assertThat;
+import static com.google.gerrit.server.fixes.testing.GitEditSubject.assertThat;
+
+import com.google.gerrit.server.fixes.FixCalculator.FixResult;
+import org.eclipse.jgit.diff.Edit;
+import org.junit.Test;
+
+public class EmptyContentTest {
+  @Test
+  public void insertSingleLineNoEOL() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("", 1, 0, 1, 0, "Abc");
+    assertThat(fixResult).text().isEqualTo("Abc");
+    assertThat(fixResult).edits().onlyElement();
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isInsert(0, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 3);
+  }
+
+  @Test
+  public void insertSingleLineWithEOL() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("", 1, 0, 1, 0, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("Abc\n");
+    assertThat(fixResult).edits().onlyElement();
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isInsert(0, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 4);
+  }
+
+  @Test
+  public void insertMultilineNoEOL() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("", 1, 0, 1, 0, "Abc\nDEFGH");
+    assertThat(fixResult).text().isEqualTo("Abc\nDEFGH");
+    assertThat(fixResult).edits().onlyElement();
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isInsert(0, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 9);
+  }
+
+  @Test
+  public void insertMultilineWithEOL() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("", 1, 0, 1, 0, "Abc\nDEFGH\n");
+    assertThat(fixResult).text().isEqualTo("Abc\nDEFGH\n");
+    assertThat(fixResult).edits().onlyElement();
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isInsert(0, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 10);
+  }
+}
diff --git a/javatests/com/google/gerrit/server/fixes/fixCalculator/FixCalculatorVariousTest.java b/javatests/com/google/gerrit/server/fixes/fixCalculator/FixCalculatorVariousTest.java
new file mode 100644
index 0000000..861af3e
--- /dev/null
+++ b/javatests/com/google/gerrit/server/fixes/fixCalculator/FixCalculatorVariousTest.java
@@ -0,0 +1,168 @@
+// Copyright (C) 2019 The Android Open Source 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 com.google.gerrit.server.fixes.fixCalculator;
+
+import static com.google.gerrit.server.fixes.testing.FixResultSubject.assertThat;
+import static com.google.gerrit.server.fixes.testing.GitEditSubject.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.Comment.Range;
+import com.google.gerrit.entities.FixReplacement;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.server.fixes.FixCalculator;
+import com.google.gerrit.server.fixes.FixCalculator.FixResult;
+import com.google.gerrit.server.patch.Text;
+import org.eclipse.jgit.diff.Edit;
+import org.junit.Test;
+
+public class FixCalculatorVariousTest {
+  private static final String multilineContentString =
+      "First line\nSecond line\nThird line\nFourth line\nFifth line\n";
+  private static final Text multilineContent = new Text(multilineContentString.getBytes(UTF_8));
+
+  public static FixResult calculateFixSingleReplacement(
+      String content, int startLine, int startChar, int endLine, int endChar, String replacement)
+      throws ResourceConflictException {
+    FixReplacement fixReplacement =
+        new FixReplacement(
+            "AnyPath", new Range(startLine, startChar, endLine, endChar), replacement);
+    return FixCalculator.calculateFix(
+        new Text(content.getBytes(UTF_8)), ImmutableList.of(fixReplacement));
+  }
+
+  @Test
+  public void lineNumberMustBePositive() {
+    assertThrows(
+        ResourceConflictException.class,
+        () -> calculateFixSingleReplacement("First line\nSecond line", 0, 0, 0, 0, "Abc"));
+  }
+
+  @Test
+  public void insertAtTheEndOfSingleLineContentHasEOLMarkInvalidPosition() throws Exception {
+    assertThrows(
+        ResourceConflictException.class,
+        () -> calculateFixSingleReplacement("First line\n", 1, 11, 1, 11, "Abc"));
+  }
+
+  @Test
+  public void severalChangesInTheSameLineNonSorted() throws Exception {
+    FixReplacement replace = new FixReplacement("path", new Range(2, 1, 2, 3), "ABC");
+    FixReplacement insert = new FixReplacement("path", new Range(2, 5, 2, 5), "DEFG");
+    FixReplacement delete = new FixReplacement("path", new Range(2, 7, 2, 9), "");
+    FixResult result =
+        FixCalculator.calculateFix(multilineContent, ImmutableList.of(replace, delete, insert));
+    assertThat(result)
+        .text()
+        .isEqualTo("First line\nSABConDEFGd ne\nThird line\nFourth line\nFifth line\n");
+    assertThat(result).edits().hasSize(1);
+    Edit edit = result.edits.get(0);
+    assertThat(edit).isReplace(1, 1, 1, 1);
+    assertThat(edit).internalEdits().hasSize(3);
+    assertThat(edit).internalEdits().element(0).isReplace(1, 2, 1, 3);
+    assertThat(edit).internalEdits().element(1).isInsert(5, 6, 4);
+    assertThat(edit).internalEdits().element(2).isDelete(7, 2, 12);
+  }
+
+  @Test
+  public void severalChangesInConsecutiveLines() throws Exception {
+    FixReplacement replace = new FixReplacement("path", new Range(2, 1, 2, 3), "ABC");
+    FixReplacement insert = new FixReplacement("path", new Range(3, 5, 3, 5), "DEFG");
+    FixReplacement delete = new FixReplacement("path", new Range(4, 7, 4, 9), "");
+    FixResult result =
+        FixCalculator.calculateFix(multilineContent, ImmutableList.of(replace, insert, delete));
+    assertThat(result)
+        .text()
+        .isEqualTo("First line\nSABCond line\nThirdDEFG line\nFourth ne\nFifth line\n");
+    assertThat(result).edits().hasSize(1);
+    Edit edit = result.edits.get(0);
+    assertThat(edit).isReplace(1, 3, 1, 3);
+    assertThat(edit).internalEdits().hasSize(3);
+    assertThat(edit).internalEdits().element(0).isReplace(1, 2, 1, 3);
+    assertThat(edit).internalEdits().element(1).isInsert(17, 18, 4);
+    assertThat(edit).internalEdits().element(2).isDelete(30, 2, 35);
+  }
+
+  @Test
+  public void severalChangesInNonConsecutiveLines() throws Exception {
+    FixReplacement replace = new FixReplacement("path", new Range(1, 1, 1, 3), "ABC");
+    FixReplacement insert = new FixReplacement("path", new Range(3, 5, 3, 5), "DEFG");
+    FixReplacement delete = new FixReplacement("path", new Range(5, 9, 6, 0), "");
+    FixResult result =
+        FixCalculator.calculateFix(multilineContent, ImmutableList.of(replace, insert, delete));
+    assertThat(result)
+        .text()
+        .isEqualTo("FABCst line\nSecond line\nThirdDEFG line\nFourth line\nFifth lin");
+    assertThat(result).edits().hasSize(3);
+    assertThat(result).edits().element(0).isReplace(0, 1, 0, 1);
+    assertThat(result).edits().element(0).internalEdits().onlyElement().isReplace(1, 2, 1, 3);
+    assertThat(result).edits().element(1).isReplace(2, 1, 2, 1);
+    assertThat(result).edits().element(1).internalEdits().onlyElement().isInsert(5, 5, 4);
+    assertThat(result).edits().element(2).isReplace(4, 1, 4, 1);
+    assertThat(result).edits().element(2).internalEdits().onlyElement().isDelete(9, 2, 9);
+  }
+
+  @Test
+  public void multipleChanges() throws Exception {
+    String str =
+        "First line\nSecond line\nThird line\nFourth line\nFifth line\nSixth line"
+            + "\nSeventh line\nEighth line\nNinth line\nTenth line\n";
+    Text content = new Text(str.getBytes(UTF_8));
+
+    FixReplacement multiLineReplace =
+        new FixReplacement("path", new Range(1, 2, 3, 3), "AB\nC\nDEFG\nQ\n");
+    FixReplacement multiLineDelete = new FixReplacement("path", new Range(4, 8, 5, 8), "");
+    FixReplacement singleLineInsert = new FixReplacement("path", new Range(5, 10, 5, 10), "QWERTY");
+
+    FixReplacement singleLineReplace = new FixReplacement("path", new Range(7, 3, 7, 7), "XY");
+    FixReplacement multiLineInsert =
+        new FixReplacement("path", new Range(8, 7, 8, 7), "KLMNO\nASDF");
+
+    FixReplacement singleLineDelete = new FixReplacement("path", new Range(10, 3, 10, 7), "");
+
+    FixResult result =
+        FixCalculator.calculateFix(
+            content,
+            ImmutableList.of(
+                multiLineReplace,
+                multiLineDelete,
+                singleLineInsert,
+                singleLineReplace,
+                multiLineInsert,
+                singleLineDelete));
+    assertThat(result)
+        .text()
+        .isEqualTo(
+            "FiAB\nC\nDEFG\nQ\nrd line\nFourth lneQWERTY\nSixth line\nSevXY line\nEighth KLMNO\nASDFline\nNinth line\nTenine\n");
+    assertThat(result).edits().hasSize(3);
+    assertThat(result).edits().element(0).isReplace(0, 5, 0, 6);
+    assertThat(result)
+        .edits()
+        .element(0)
+        .internalEdits()
+        .containsExactly(
+            new Edit(2, 26, 2, 14), new Edit(42, 54, 30, 30), new Edit(56, 56, 32, 38));
+
+    assertThat(result).edits().element(1).isReplace(6, 2, 7, 3);
+    assertThat(result)
+        .edits()
+        .element(1)
+        .internalEdits()
+        .containsExactly(new Edit(3, 7, 3, 5), new Edit(20, 20, 18, 28));
+    assertThat(result).edits().element(2).isReplace(9, 1, 11, 1);
+    assertThat(result).edits().element(2).internalEdits().onlyElement().isDelete(3, 4, 3);
+  }
+}
diff --git a/javatests/com/google/gerrit/server/fixes/fixCalculator/MultilineContentNoEOLTest.java b/javatests/com/google/gerrit/server/fixes/fixCalculator/MultilineContentNoEOLTest.java
new file mode 100644
index 0000000..dd36e3a
--- /dev/null
+++ b/javatests/com/google/gerrit/server/fixes/fixCalculator/MultilineContentNoEOLTest.java
@@ -0,0 +1,337 @@
+// Copyright (C) 2019 The Android Open Source 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 com.google.gerrit.server.fixes.fixCalculator;
+
+import static com.google.gerrit.server.fixes.testing.FixResultSubject.assertThat;
+import static com.google.gerrit.server.fixes.testing.GitEditSubject.assertThat;
+
+import com.google.gerrit.server.fixes.FixCalculator.FixResult;
+import org.eclipse.jgit.diff.Edit;
+import org.junit.Test;
+
+public class MultilineContentNoEOLTest {
+
+  @Test
+  public void insertSingleLineNoEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 1, 0, 1, 0, "Abc");
+    assertThat(fixResult).text().isEqualTo("AbcFirst line\nSecond line\nThird line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 3);
+  }
+
+  @Test
+  public void insertSingleLineNoEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 2, 5, 2, 5, "Abc");
+    assertThat(fixResult).text().isEqualTo("First line\nSeconAbcd line\nThird line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(1, 1, 1, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(5, 5, 3);
+  }
+
+  @Test
+  public void insertSingleLineNoEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 3, 10, 3, 10, "Abc");
+    assertThat(fixResult).text().isEqualTo("First line\nSecond line\nThird lineAbc");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(2, 1, 2, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(10, 10, 3);
+  }
+
+  @Test
+  public void insertSingleLineWithEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 1, 0, 1, 0, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("Abc\nFirst line\nSecond line\nThird line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isInsert(0, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 4);
+  }
+
+  @Test
+  public void insertSingleLineWithEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 2, 5, 2, 5, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("First line\nSeconAbc\nd line\nThird line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(1, 1, 1, 2);
+    assertThat(edit).internalEdits().onlyElement().isInsert(5, 5, 4);
+  }
+
+  @Test
+  public void insertSingleLineWithEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 3, 10, 3, 10, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("First line\nSecond line\nThird lineAbc\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(2, 1, 2, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(10, 10, 4);
+  }
+
+  @Test
+  public void insertMultilineLineWithEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 1, 0, 1, 0, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("Abc\nDefgh\nFirst line\nSecond line\nThird line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isInsert(0, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 10);
+  }
+
+  @Test
+  public void insertMultilineLineWithEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 2, 5, 2, 5, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("First line\nSeconAbc\nDefgh\nd line\nThird line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(1, 1, 1, 3);
+    assertThat(edit).internalEdits().onlyElement().isInsert(5, 5, 10);
+  }
+
+  @Test
+  public void insertMultilineLineWithEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 3, 10, 3, 10, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("First line\nSecond line\nThird lineAbc\nDefgh\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(2, 1, 2, 2);
+    assertThat(edit).internalEdits().onlyElement().isInsert(10, 10, 10);
+  }
+
+  @Test
+  public void replaceWithSingleLineNoEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 1, 0, 1, 2, "Abc");
+    assertThat(fixResult).text().isEqualTo("Abcrst line\nSecond line\nThird line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isReplace(0, 2, 0, 3);
+  }
+
+  @Test
+  public void replaceWithSingleLineNoEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 2, 3, 2, 5, "Abc");
+    assertThat(fixResult).text().isEqualTo("First line\nSecAbcd line\nThird line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(1, 1, 1, 1);
+    assertThat(edit).internalEdits().onlyElement().isReplace(3, 2, 3, 3);
+  }
+
+  @Test
+  public void replaceWithSingleLineNoEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 3, 8, 3, 10, "Abc");
+    assertThat(fixResult).text().isEqualTo("First line\nSecond line\nThird liAbc");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(2, 1, 2, 1);
+    assertThat(edit).internalEdits().onlyElement().isReplace(8, 2, 8, 3);
+  }
+
+  @Test
+  public void replaceWithSingleLineWithEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 1, 0, 1, 2, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("Abc\nrst line\nSecond line\nThird line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(0, 2, 0, 4);
+  }
+
+  @Test
+  public void replaceWithSingleLineWithEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 2, 3, 2, 5, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("First line\nSecAbc\nd line\nThird line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(1, 1, 1, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(3, 2, 3, 4);
+  }
+
+  @Test
+  public void replaceWithSingleLineWithEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 3, 8, 3, 10, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("First line\nSecond line\nThird liAbc\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(2, 1, 2, 1);
+    assertThat(edit).internalEdits().onlyElement().isReplace(8, 2, 8, 4);
+  }
+
+  @Test
+  public void replaceMultilineLineWithEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 1, 0, 1, 2, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("Abc\nDefgh\nrst line\nSecond line\nThird line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 3);
+    assertThat(edit).internalEdits().onlyElement().isReplace(0, 2, 0, 10);
+  }
+
+  @Test
+  public void replaceMultilineLineWithEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 2, 3, 2, 5, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("First line\nSecAbc\nDefgh\nd line\nThird line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(1, 1, 1, 3);
+    assertThat(edit).internalEdits().onlyElement().isReplace(3, 2, 3, 10);
+  }
+
+  @Test
+  public void replaceMultilineLineWithEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 3, 8, 3, 10, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("First line\nSecond line\nThird liAbc\nDefgh\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(2, 1, 2, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(8, 2, 8, 10);
+  }
+
+  @Test
+  public void replaceMultilineLineNoEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 1, 0, 1, 2, "Abc\nDefgh");
+    assertThat(fixResult).text().isEqualTo("Abc\nDefghrst line\nSecond line\nThird line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(0, 2, 0, 9);
+  }
+
+  @Test
+  public void replaceMultilineLineNoEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 2, 3, 2, 5, "Abc\nDefgh");
+    assertThat(fixResult).text().isEqualTo("First line\nSecAbc\nDefghd line\nThird line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(1, 1, 1, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(3, 2, 3, 9);
+  }
+
+  @Test
+  public void replaceMultilineLineNoEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 3, 8, 3, 10, "Abc\nDefgh");
+    assertThat(fixResult).text().isEqualTo("First line\nSecond line\nThird liAbc\nDefgh");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(2, 1, 2, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(8, 2, 8, 9);
+  }
+
+  @Test
+  public void replaceWholeContent() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 1, 0, 3, 10, "Abc");
+    assertThat(fixResult).text().isEqualTo("Abc");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 3, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isReplace(0, 33, 0, 3);
+  }
+
+  @Test
+  public void deleteWholeContent() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 1, 0, 3, 10, "");
+    assertThat(fixResult).text().isEqualTo("");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isDelete(0, 3, 0);
+    assertThat(edit).internalEdits().onlyElement().isDelete(0, 33, 0);
+  }
+
+  @Test
+  public void deleteAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 1, 0, 1, 4, "");
+    assertThat(fixResult).text().isEqualTo("t line\nSecond line\nThird line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isDelete(0, 4, 0);
+  }
+
+  @Test
+  public void deleteInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 1, 5, 3, 1, "");
+    assertThat(fixResult).text().isEqualTo("Firsthird line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 3, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isDelete(5, 19, 5);
+  }
+
+  @Test
+  public void deleteAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line", 3, 7, 3, 10, "");
+    assertThat(fixResult).text().isEqualTo("First line\nSecond line\nThird l");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(2, 1, 2, 1);
+    assertThat(edit).internalEdits().onlyElement().isDelete(7, 3, 7);
+  }
+}
diff --git a/javatests/com/google/gerrit/server/fixes/fixCalculator/MultilineContentWithEOLTest.java b/javatests/com/google/gerrit/server/fixes/fixCalculator/MultilineContentWithEOLTest.java
new file mode 100644
index 0000000..a2868c8
--- /dev/null
+++ b/javatests/com/google/gerrit/server/fixes/fixCalculator/MultilineContentWithEOLTest.java
@@ -0,0 +1,337 @@
+// Copyright (C) 2019 The Android Open Source 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 com.google.gerrit.server.fixes.fixCalculator;
+
+import static com.google.gerrit.server.fixes.testing.FixResultSubject.assertThat;
+import static com.google.gerrit.server.fixes.testing.GitEditSubject.assertThat;
+
+import com.google.gerrit.server.fixes.FixCalculator.FixResult;
+import org.eclipse.jgit.diff.Edit;
+import org.junit.Test;
+
+public class MultilineContentWithEOLTest {
+
+  @Test
+  public void insertSingleLineNoEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 1, 0, 1, 0, "Abc");
+    assertThat(fixResult).text().isEqualTo("AbcFirst line\nSecond line\nThird line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 3);
+  }
+
+  @Test
+  public void insertSingleLineNoEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 2, 5, 2, 5, "Abc");
+    assertThat(fixResult).text().isEqualTo("First line\nSeconAbcd line\nThird line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(1, 1, 1, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(5, 5, 3);
+  }
+
+  @Test
+  public void insertSingleLineNoEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 4, 0, 4, 0, "Abc");
+    assertThat(fixResult).text().isEqualTo("First line\nSecond line\nThird line\nAbc");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isInsert(3, 3, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 3);
+  }
+
+  @Test
+  public void insertSingleLineWithEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 1, 0, 1, 0, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("Abc\nFirst line\nSecond line\nThird line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isInsert(0, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 4);
+  }
+
+  @Test
+  public void insertSingleLineWithEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 2, 5, 2, 5, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("First line\nSeconAbc\nd line\nThird line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(1, 1, 1, 2);
+    assertThat(edit).internalEdits().onlyElement().isInsert(5, 5, 4);
+  }
+
+  @Test
+  public void insertSingleLineWithEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 4, 0, 4, 0, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("First line\nSecond line\nThird line\nAbc\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isInsert(3, 3, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 4);
+  }
+
+  @Test
+  public void insertMultilineLineWithEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 1, 0, 1, 0, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("Abc\nDefgh\nFirst line\nSecond line\nThird line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isInsert(0, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 10);
+  }
+
+  @Test
+  public void insertMultilineLineWithEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 2, 5, 2, 5, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("First line\nSeconAbc\nDefgh\nd line\nThird line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(1, 1, 1, 3);
+    assertThat(edit).internalEdits().onlyElement().isInsert(5, 5, 10);
+  }
+
+  @Test
+  public void insertMultilineLineWithEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 4, 0, 4, 0, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("First line\nSecond line\nThird line\nAbc\nDefgh\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isInsert(3, 3, 2);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 10);
+  }
+
+  @Test
+  public void replaceWithSingleLineNoEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 1, 0, 1, 2, "Abc");
+    assertThat(fixResult).text().isEqualTo("Abcrst line\nSecond line\nThird line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isReplace(0, 2, 0, 3);
+  }
+
+  @Test
+  public void replaceWithSingleLineNoEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 2, 3, 2, 5, "Abc");
+    assertThat(fixResult).text().isEqualTo("First line\nSecAbcd line\nThird line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(1, 1, 1, 1);
+    assertThat(edit).internalEdits().onlyElement().isReplace(3, 2, 3, 3);
+  }
+
+  @Test
+  public void replaceWithSingleLineNoEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 3, 9, 4, 0, "Abc");
+    assertThat(fixResult).text().isEqualTo("First line\nSecond line\nThird linAbc");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(2, 1, 2, 1);
+    assertThat(edit).internalEdits().onlyElement().isReplace(9, 2, 9, 3);
+  }
+
+  @Test
+  public void replaceWithSingleLineWithEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 1, 0, 1, 2, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("Abc\nrst line\nSecond line\nThird line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(0, 2, 0, 4);
+  }
+
+  @Test
+  public void replaceWithSingleLineWithEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 2, 3, 2, 5, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("First line\nSecAbc\nd line\nThird line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(1, 1, 1, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(3, 2, 3, 4);
+  }
+
+  @Test
+  public void replaceWithSingleLineWithEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 3, 9, 4, 0, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("First line\nSecond line\nThird linAbc\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(2, 1, 2, 1);
+    assertThat(edit).internalEdits().onlyElement().isReplace(9, 2, 9, 4);
+  }
+
+  @Test
+  public void replaceMultilineLineWithEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 1, 0, 1, 2, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("Abc\nDefgh\nrst line\nSecond line\nThird line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 3);
+    assertThat(edit).internalEdits().onlyElement().isReplace(0, 2, 0, 10);
+  }
+
+  @Test
+  public void replaceMultilineLineWithEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 2, 3, 2, 5, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("First line\nSecAbc\nDefgh\nd line\nThird line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(1, 1, 1, 3);
+    assertThat(edit).internalEdits().onlyElement().isReplace(3, 2, 3, 10);
+  }
+
+  @Test
+  public void replaceMultilineLineWithEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 3, 9, 4, 0, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("First line\nSecond line\nThird linAbc\nDefgh\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(2, 1, 2, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(9, 2, 9, 10);
+  }
+
+  @Test
+  public void replaceMultilineLineNoEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 1, 0, 1, 2, "Abc\nDefgh");
+    assertThat(fixResult).text().isEqualTo("Abc\nDefghrst line\nSecond line\nThird line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(0, 2, 0, 9);
+  }
+
+  @Test
+  public void replaceMultilineLineNoEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 2, 3, 2, 5, "Abc\nDefgh");
+    assertThat(fixResult).text().isEqualTo("First line\nSecAbc\nDefghd line\nThird line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(1, 1, 1, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(3, 2, 3, 9);
+  }
+
+  @Test
+  public void replaceMultilineLineNoEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 3, 9, 4, 0, "Abc\nDefgh");
+    assertThat(fixResult).text().isEqualTo("First line\nSecond line\nThird linAbc\nDefgh");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(2, 1, 2, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(9, 2, 9, 9);
+  }
+
+  @Test
+  public void replaceWholeContent() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 1, 0, 4, 0, "Abc");
+    assertThat(fixResult).text().isEqualTo("Abc");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 3, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isReplace(0, 34, 0, 3);
+  }
+
+  @Test
+  public void deleteWholeContent() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 1, 0, 4, 0, "");
+    assertThat(fixResult).text().isEqualTo("");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isDelete(0, 3, 0);
+    assertThat(edit).internalEdits().onlyElement().isDelete(0, 34, 0);
+  }
+
+  @Test
+  public void deleteAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 1, 0, 1, 4, "");
+    assertThat(fixResult).text().isEqualTo("t line\nSecond line\nThird line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isDelete(0, 4, 0);
+  }
+
+  @Test
+  public void deleteInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 1, 5, 3, 1, "");
+    assertThat(fixResult).text().isEqualTo("Firsthird line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 3, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isDelete(5, 19, 5);
+  }
+
+  @Test
+  public void deleteAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\nSecond line\nThird line\n", 3, 7, 4, 0, "");
+    assertThat(fixResult).text().isEqualTo("First line\nSecond line\nThird l");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(2, 1, 2, 1);
+    assertThat(edit).internalEdits().onlyElement().isDelete(7, 4, 7);
+  }
+}
diff --git a/javatests/com/google/gerrit/server/fixes/fixCalculator/OneLineContentNoEOLTest.java b/javatests/com/google/gerrit/server/fixes/fixCalculator/OneLineContentNoEOLTest.java
new file mode 100644
index 0000000..3de4ef7
--- /dev/null
+++ b/javatests/com/google/gerrit/server/fixes/fixCalculator/OneLineContentNoEOLTest.java
@@ -0,0 +1,320 @@
+// Copyright (C) 2019 The Android Open Source 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 com.google.gerrit.server.fixes.fixCalculator;
+
+import static com.google.gerrit.server.fixes.testing.FixResultSubject.assertThat;
+import static com.google.gerrit.server.fixes.testing.GitEditSubject.assertThat;
+
+import com.google.gerrit.server.fixes.FixCalculator.FixResult;
+import org.eclipse.jgit.diff.Edit;
+import org.junit.Test;
+
+public class OneLineContentNoEOLTest {
+
+  @Test
+  public void insertSingleLineNoEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line", 1, 0, 1, 0, "Abc");
+    assertThat(fixResult).text().isEqualTo("AbcFirst line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 3);
+  }
+
+  @Test
+  public void insertSingleLineNoEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line", 1, 5, 1, 5, "Abc");
+    assertThat(fixResult).text().isEqualTo("FirstAbc line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(5, 5, 3);
+  }
+
+  @Test
+  public void insertSingleLineNoEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line", 1, 10, 1, 10, "Abc");
+    assertThat(fixResult).text().isEqualTo("First lineAbc");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(10, 10, 3);
+  }
+
+  @Test
+  public void insertSingleLineWithEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line", 1, 0, 1, 0, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("Abc\nFirst line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isInsert(0, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 4);
+  }
+
+  @Test
+  public void insertSingleLineWithEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line", 1, 5, 1, 5, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("FirstAbc\n line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isInsert(5, 5, 4);
+  }
+
+  @Test
+  public void insertSingleLineWithEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line", 1, 10, 1, 10, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("First lineAbc\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(10, 10, 4);
+  }
+
+  @Test
+  public void insertMultilineLineWithEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line", 1, 0, 1, 0, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("Abc\nDefgh\nFirst line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isInsert(0, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 10);
+  }
+
+  @Test
+  public void insertMultilineLineWithEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line", 1, 5, 1, 5, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("FirstAbc\nDefgh\n line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 3);
+    assertThat(edit).internalEdits().onlyElement().isInsert(5, 5, 10);
+  }
+
+  @Test
+  public void insertMultilineLineWithEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line", 1, 10, 1, 10, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("First lineAbc\nDefgh\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isInsert(10, 10, 10);
+  }
+
+  @Test
+  public void replaceWithSingleLineNoEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line", 1, 0, 1, 2, "Abc");
+    assertThat(fixResult).text().isEqualTo("Abcrst line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isReplace(0, 2, 0, 3);
+  }
+
+  @Test
+  public void replaceWithSingleLineNoEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line", 1, 3, 1, 5, "Abc");
+    assertThat(fixResult).text().isEqualTo("FirAbc line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isReplace(3, 2, 3, 3);
+  }
+
+  @Test
+  public void replaceWithSingleLineNoEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line", 1, 8, 1, 10, "Abc");
+    assertThat(fixResult).text().isEqualTo("First liAbc");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isReplace(8, 2, 8, 3);
+  }
+
+  @Test
+  public void replaceWithSingleLineWithEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line", 1, 0, 1, 2, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("Abc\nrst line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(0, 2, 0, 4);
+  }
+
+  @Test
+  public void replaceWithSingleLineWithEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line", 1, 3, 1, 5, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("FirAbc\n line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(3, 2, 3, 4);
+  }
+
+  @Test
+  public void replaceWithSingleLineWithEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line", 1, 8, 1, 10, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("First liAbc\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isReplace(8, 2, 8, 4);
+  }
+
+  @Test
+  public void replaceMultilineLineWithEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line", 1, 0, 1, 2, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("Abc\nDefgh\nrst line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 3);
+    assertThat(edit).internalEdits().onlyElement().isReplace(0, 2, 0, 10);
+  }
+
+  @Test
+  public void replaceMultilineLineWithEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line", 1, 3, 1, 5, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("FirAbc\nDefgh\n line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 3);
+    assertThat(edit).internalEdits().onlyElement().isReplace(3, 2, 3, 10);
+  }
+
+  @Test
+  public void replaceMultilineLineWithEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line", 1, 8, 1, 10, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("First liAbc\nDefgh\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(8, 2, 8, 10);
+  }
+
+  @Test
+  public void replaceMultilineLineNoEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line", 1, 0, 1, 2, "Abc\nDefgh");
+    assertThat(fixResult).text().isEqualTo("Abc\nDefghrst line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(0, 2, 0, 9);
+  }
+
+  @Test
+  public void replaceMultilineLineNoEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line", 1, 3, 1, 5, "Abc\nDefgh");
+    assertThat(fixResult).text().isEqualTo("FirAbc\nDefgh line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(3, 2, 3, 9);
+  }
+
+  @Test
+  public void replaceMultilineLineNoEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line", 1, 8, 1, 10, "Abc\nDefgh");
+    assertThat(fixResult).text().isEqualTo("First liAbc\nDefgh");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(8, 2, 8, 9);
+  }
+
+  @Test
+  public void replaceWholeContent() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line", 1, 0, 1, 10, "Abc");
+    assertThat(fixResult).text().isEqualTo("Abc");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isReplace(0, 10, 0, 3);
+  }
+
+  @Test
+  public void deleteWholeContent() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line", 1, 0, 1, 10, "");
+    assertThat(fixResult).text().isEqualTo("");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isDelete(0, 1, 0);
+    assertThat(edit).internalEdits().onlyElement().isDelete(0, 10, 0);
+  }
+
+  @Test
+  public void deleteAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line", 1, 0, 1, 4, "");
+    assertThat(fixResult).text().isEqualTo("t line");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isDelete(0, 4, 0);
+  }
+
+  @Test
+  public void deleteInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line", 1, 5, 1, 8, "");
+    assertThat(fixResult).text().isEqualTo("Firstne");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isDelete(5, 3, 5);
+  }
+
+  @Test
+  public void deleteAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line", 1, 7, 1, 10, "");
+    assertThat(fixResult).text().isEqualTo("First l");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isDelete(7, 3, 7);
+  }
+}
diff --git a/javatests/com/google/gerrit/server/fixes/fixCalculator/OneLineContentWithEOLTest.java b/javatests/com/google/gerrit/server/fixes/fixCalculator/OneLineContentWithEOLTest.java
new file mode 100644
index 0000000..bae714b
--- /dev/null
+++ b/javatests/com/google/gerrit/server/fixes/fixCalculator/OneLineContentWithEOLTest.java
@@ -0,0 +1,320 @@
+// Copyright (C) 2019 The Android Open Source 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 com.google.gerrit.server.fixes.fixCalculator;
+
+import static com.google.gerrit.server.fixes.testing.FixResultSubject.assertThat;
+import static com.google.gerrit.server.fixes.testing.GitEditSubject.assertThat;
+
+import com.google.gerrit.server.fixes.FixCalculator.FixResult;
+import org.eclipse.jgit.diff.Edit;
+import org.junit.Test;
+
+public class OneLineContentWithEOLTest {
+
+  @Test
+  public void insertSingleLineNoEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line\n", 1, 0, 1, 0, "Abc");
+    assertThat(fixResult).text().isEqualTo("AbcFirst line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 3);
+  }
+
+  @Test
+  public void insertSingleLineNoEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line\n", 1, 5, 1, 5, "Abc");
+    assertThat(fixResult).text().isEqualTo("FirstAbc line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(5, 5, 3);
+  }
+
+  @Test
+  public void insertSingleLineNoEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line\n", 2, 0, 2, 0, "Abc");
+    assertThat(fixResult).text().isEqualTo("First line\nAbc");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isInsert(1, 1, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 3);
+  }
+
+  @Test
+  public void insertSingleLineWithEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line\n", 1, 0, 1, 0, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("Abc\nFirst line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isInsert(0, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 4);
+  }
+
+  @Test
+  public void insertSingleLineWithEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line\n", 1, 5, 1, 5, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("FirstAbc\n line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isInsert(5, 5, 4);
+  }
+
+  @Test
+  public void insertSingleLineWithEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line\n", 2, 0, 2, 0, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("First line\nAbc\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isInsert(1, 1, 1);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 4);
+  }
+
+  @Test
+  public void insertMultilineLineWithEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\n", 1, 0, 1, 0, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("Abc\nDefgh\nFirst line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isInsert(0, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 10);
+  }
+
+  @Test
+  public void insertMultilineLineWithEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\n", 1, 5, 1, 5, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("FirstAbc\nDefgh\n line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 3);
+    assertThat(edit).internalEdits().onlyElement().isInsert(5, 5, 10);
+  }
+
+  @Test
+  public void insertMultilineLineWithEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\n", 2, 0, 2, 0, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("First line\nAbc\nDefgh\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isInsert(1, 1, 2);
+    assertThat(edit).internalEdits().onlyElement().isInsert(0, 0, 10);
+  }
+
+  @Test
+  public void replaceWithSingleLineNoEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line\n", 1, 0, 1, 2, "Abc");
+    assertThat(fixResult).text().isEqualTo("Abcrst line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isReplace(0, 2, 0, 3);
+  }
+
+  @Test
+  public void replaceWithSingleLineNoEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line\n", 1, 3, 1, 5, "Abc");
+    assertThat(fixResult).text().isEqualTo("FirAbc line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isReplace(3, 2, 3, 3);
+  }
+
+  @Test
+  public void replaceWithSingleLineNoEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line\n", 1, 9, 2, 0, "Abc");
+    assertThat(fixResult).text().isEqualTo("First linAbc");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isReplace(9, 2, 9, 3);
+  }
+
+  @Test
+  public void replaceWithSingleLineWithEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line\n", 1, 0, 1, 2, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("Abc\nrst line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(0, 2, 0, 4);
+  }
+
+  @Test
+  public void replaceWithSingleLineWithEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line\n", 1, 3, 1, 5, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("FirAbc\n line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(3, 2, 3, 4);
+  }
+
+  @Test
+  public void replaceWithSingleLineWithEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line\n", 1, 8, 2, 0, "Abc\n");
+    assertThat(fixResult).text().isEqualTo("First liAbc\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isReplace(8, 3, 8, 4);
+  }
+
+  @Test
+  public void replaceMultilineLineWithEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\n", 1, 0, 1, 2, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("Abc\nDefgh\nrst line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 3);
+    assertThat(edit).internalEdits().onlyElement().isReplace(0, 2, 0, 10);
+  }
+
+  @Test
+  public void replaceMultilineLineWithEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\n", 1, 3, 1, 5, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("FirAbc\nDefgh\n line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 3);
+    assertThat(edit).internalEdits().onlyElement().isReplace(3, 2, 3, 10);
+  }
+
+  @Test
+  public void replaceMultilineLineWithEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\n", 1, 8, 2, 0, "Abc\nDefgh\n");
+    assertThat(fixResult).text().isEqualTo("First liAbc\nDefgh\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(8, 3, 8, 10);
+  }
+
+  @Test
+  public void replaceMultilineLineNoEOLAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\n", 1, 0, 1, 2, "Abc\nDefgh");
+    assertThat(fixResult).text().isEqualTo("Abc\nDefghrst line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(0, 2, 0, 9);
+  }
+
+  @Test
+  public void replaceMultilineLineNoEOLInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\n", 1, 3, 1, 5, "Abc\nDefgh");
+    assertThat(fixResult).text().isEqualTo("FirAbc\nDefgh line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(3, 2, 3, 9);
+  }
+
+  @Test
+  public void replaceMultilineLineNoEOLAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement(
+            "First line\n", 1, 8, 2, 0, "Abc\nDefgh");
+    assertThat(fixResult).text().isEqualTo("First liAbc\nDefgh");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 2);
+    assertThat(edit).internalEdits().onlyElement().isReplace(8, 3, 8, 9);
+  }
+
+  @Test
+  public void replaceWholeContent() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line\n", 1, 0, 2, 0, "Abc");
+    assertThat(fixResult).text().isEqualTo("Abc");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isReplace(0, 11, 0, 3);
+  }
+
+  @Test
+  public void deleteWholeContent() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line\n", 1, 0, 2, 0, "");
+    assertThat(fixResult).text().isEqualTo("");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isDelete(0, 1, 0);
+    assertThat(edit).internalEdits().onlyElement().isDelete(0, 11, 0);
+  }
+
+  @Test
+  public void deleteAtStart() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line\n", 1, 0, 1, 4, "");
+    assertThat(fixResult).text().isEqualTo("t line\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isDelete(0, 4, 0);
+  }
+
+  @Test
+  public void deleteInTheMiddle() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line\n", 1, 5, 1, 8, "");
+    assertThat(fixResult).text().isEqualTo("Firstne\n");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isDelete(5, 3, 5);
+  }
+
+  @Test
+  public void deleteAtEnd() throws Exception {
+    FixResult fixResult =
+        FixCalculatorVariousTest.calculateFixSingleReplacement("First line\n", 1, 7, 2, 0, "");
+    assertThat(fixResult).text().isEqualTo("First l");
+    assertThat(fixResult).edits().hasSize(1);
+    Edit edit = fixResult.edits.get(0);
+    assertThat(edit).isReplace(0, 1, 0, 1);
+    assertThat(edit).internalEdits().onlyElement().isDelete(7, 4, 7);
+  }
+}