| /* |
| * 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; |
| } |
| } |