blob: 2b81d32469bde36ade9fb3853ed2acb2c274d2e1 [file] [log] [blame]
/*
* Copyright 2000-2010 JetBrains s.r.o.
*
* 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.intellij.openapi.diff.impl.patch.apply;
import com.intellij.openapi.diff.impl.patch.ApplyPatchException;
import com.intellij.openapi.diff.impl.patch.ApplyPatchStatus;
import com.intellij.openapi.diff.impl.patch.PatchHunk;
import com.intellij.openapi.diff.impl.patch.PatchLine;
import java.util.ArrayList;
import java.util.List;
public class ApplyPatchHunk {
private final PatchHunk myHunk;
public ApplyPatchHunk(final PatchHunk hunk) {
myHunk = hunk;
}
public ApplyPatchStatus apply(final List<String> lines) throws ApplyPatchException {
List<String> originalLines = new ArrayList<String>(lines);
try {
return tryApply(lines, false);
}
catch(ApplyPatchException ex) {
lines.clear();
lines.addAll(originalLines);
return tryApply(lines, true);
}
}
private ApplyPatchStatus tryApply(final List<String> lines, boolean acceptPartial) throws ApplyPatchException {
final List<PatchLine> hunkLines = myHunk.getLines();
ApplyPatchStatus result = null;
int curLine = findStartLine(hunkLines, lines);
for(PatchLine line: hunkLines) {
final String patchLineText = line.getText();
switch (line.getType()) {
case CONTEXT:
checkContextMismatch(lines, curLine, patchLineText);
curLine++;
break;
case ADD:
if (curLine < lines.size() && lines.get(curLine).equals(patchLineText) && acceptPartial) {
result = ApplyPatchStatus.and(result, ApplyPatchStatus.ALREADY_APPLIED);
}
else {
lines.add(curLine, patchLineText);
result = ApplyPatchStatus.and(result, ApplyPatchStatus.SUCCESS);
}
curLine++;
break;
case REMOVE:
if (curLine >= lines.size() || !patchLineText.equals(lines.get(curLine))) {
if (acceptPartial) {
// we'll get a context mismatch exception later if it's actually a conflict and not an already applied line
result = ApplyPatchStatus.and(result, ApplyPatchStatus.ALREADY_APPLIED);
}
else {
checkContextMismatch(lines, curLine, patchLineText);
}
}
else {
lines.remove(curLine);
result = ApplyPatchStatus.and(result, ApplyPatchStatus.SUCCESS);
}
break;
}
}
if (result != null) {
return result;
}
return ApplyPatchStatus.SUCCESS;
}
private static void checkContextMismatch(final List<String> lines, final int curLine, final String patchLineText) throws ApplyPatchException {
if (curLine >= lines.size()) {
throw new ApplyPatchException("Unexpected end of document. Expected line:\n" + patchLineText);
}
if (!patchLineText.equals(lines.get(curLine))) {
throw new ApplyPatchException("Context mismatch. Expected line:\n" + patchLineText + "\nFound line:\n" + lines.get(curLine));
}
}
private int findStartLine(final List<PatchLine> hunkLines, final List<String> lines) throws ApplyPatchException {
int totalContextLines = countContextLines(hunkLines);
final int startLineBefore = myHunk.getStartLineBefore();
if (getLinesProcessingContext(hunkLines, lines, startLineBefore) == totalContextLines) {
return startLineBefore;
}
int maxContextStartLine = -1;
int maxContextLines = 0;
for(int i=0;i< lines.size(); i++) {
int contextLines = getLinesProcessingContext(hunkLines, lines, i);
if (contextLines == totalContextLines) {
return i;
}
if (contextLines > maxContextLines) {
maxContextLines = contextLines;
maxContextStartLine = i;
}
}
if (maxContextLines < 2) {
throw new ApplyPatchException("couldn't find context");
}
return maxContextStartLine;
}
private int countContextLines(final List<PatchLine> hunkLines) {
int count = 0;
for(PatchLine line: hunkLines) {
if (line.getType() == PatchLine.Type.CONTEXT || line.getType() == PatchLine.Type.REMOVE) {
count++;
}
}
return count;
}
private int getLinesProcessingContext(final List<PatchLine> hunkLines, final List<String> lines, int startLine) {
int count = 0;
for(PatchLine line: hunkLines) {
PatchLine.Type type = line.getType();
if (type == PatchLine.Type.REMOVE || type == PatchLine.Type.CONTEXT) {
// TODO: smarter algorithm (search outward from non-context lines)
if (startLine >= lines.size() || !line.getText().equals(lines.get(startLine))) {
return count;
}
count++;
startLine++;
}
}
return count;
}
}