Java tutorial
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Contributor(s): * * The Original Software is NetBeans. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun * Microsystems, Inc. All Rights Reserved. * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ package jk_5.nailed.mcp.patching; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.StringReader; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.cloudbees.diff.Hunk; import com.cloudbees.diff.PatchException; import org.apache.commons.io.IOUtils; /** * STOLEN FROM diff4j v1.1 * * Applies contextual patches to files. The patch file can contain patches for multiple files. * * @author Maros Sandor */ public final class ContextualPatch { public static final String MAGIC = "# This patch file was generated by NetBeans IDE"; // NOI18N // first seen in mercurial diffs: characters after the second @@ - ignore them private final Pattern unifiedRangePattern = Pattern.compile("@@ -(\\d+)(,\\d+)? \\+(\\d+)(,\\d+)? @@(\\s.*)?"); private final Pattern baseRangePattern = Pattern.compile("\\*\\*\\* (\\d+)(,\\d+)? \\*\\*\\*\\*"); private final Pattern modifiedRangePattern = Pattern.compile("--- (\\d+)(,\\d+)? ----"); private final Pattern normalChangeRangePattern = Pattern.compile("(\\d+),(\\d+)c(\\d+),(\\d+)"); private final Pattern normalAddRangePattern = Pattern.compile("(\\d+)a(\\d+),(\\d+)"); private final Pattern normalDeleteRangePattern = Pattern.compile("(\\d+),(\\d+)d(\\d+)"); private final Pattern binaryHeaderPattern = Pattern.compile("MIME: (.*?); encoding: (.*?); length: (-?\\d+?)"); private final File patchFile; private final File suggestedContext; private String patchString; private IContextProvider contextProvider; private int maxFuzz = 0; private boolean c14nWhitespace = false; private boolean c14nAccess = false; private File context; private BufferedReader patchReader; private String patchLine; private boolean patchLineRead; private int lastPatchedLine; // the last line that was successfuly patched public static ContextualPatch create(File patchFile, File context) { return new ContextualPatch(patchFile, context); } public static ContextualPatch create(String patchString, IContextProvider context) { return new ContextualPatch(patchString, context); } private ContextualPatch(String patchString, IContextProvider context) { this.patchString = patchString; this.contextProvider = context; patchFile = null; suggestedContext = null; } private ContextualPatch(File patchFile, File context) { this.patchFile = patchFile; this.suggestedContext = context; } public ContextualPatch setMaxFuzz(int maxFuzz) { this.maxFuzz = maxFuzz; return this; } public ContextualPatch setWhitespaceC14N(boolean canonicalize) { this.c14nWhitespace = canonicalize; return this; } public ContextualPatch setAccessC14N(boolean canonicalize) { this.c14nAccess = canonicalize; return this; } /** * @param dryRun true if the method should not make any modifications to files, false otherwise * @return * @throws PatchException * @throws IOException */ public List<PatchReport> patch(boolean dryRun) throws PatchException, IOException { List<PatchReport> report = new ArrayList<PatchReport>(); init(); try { patchLine = patchReader.readLine(); List<SinglePatch> patches = new ArrayList<SinglePatch>(); for (;;) { SinglePatch patch = getNextPatch(); if (patch == null) { break; } patches.add(patch); } computeContext(patches); for (SinglePatch patch : patches) { try { report.add(applyPatch(patch, dryRun)); //report.add(new PatchReport(patch.targetFile, computeBackup(patch.targetFile), patch.binary, PatchStatus.Patched, null)); } catch (Exception e) { report.add(new PatchReport(patch.targetPath, patch.binary, PatchStatus.Failure, e, new ArrayList<HunkReport>())); } } return report; } finally { if (patchReader != null) { try { patchReader.close(); } catch (IOException e) { } } } } private void init() throws IOException { if (patchString != null) { //Just read the string as is, without trying to read the magic/encoding as the string shuldn't need encoding! patchReader = new BufferedReader(new StringReader(patchString)); return; } patchReader = new BufferedReader(new FileReader(patchFile)); String encoding = "ISO-8859-1"; String line = patchReader.readLine(); if (MAGIC.equals(line)) { encoding = "utf8"; // NOI18N line = patchReader.readLine(); } patchReader.close(); byte[] buffer = new byte[MAGIC.length()]; InputStream in = new FileInputStream(patchFile); int read = in.read(buffer); in.close(); if (read != -1 && MAGIC.equals(new String(buffer, "utf8"))) { // NOI18N encoding = "utf8"; // NOI18N } patchReader = new BufferedReader(new InputStreamReader(new FileInputStream(patchFile), encoding)); } private PatchReport applyPatch(SinglePatch patch, boolean dryRun) throws IOException, PatchException { lastPatchedLine = 1; List<HunkReport> ret = new ArrayList<HunkReport>(); if (this.contextProvider != null) { List<String> target = contextProvider.getData(patch.targetPath); if (target != null && !patch.binary) { if (patchCreatesNewFileThatAlreadyExists(patch, target)) { //Check if the patch doesn't need to be applied... for (int x = 0; x < patch.hunks.length; x++) { ret.add(new HunkReport(PatchStatus.Skipped, null, 0, 0, x)); } return new PatchReport(patch.targetPath, patch.binary, PatchStatus.Skipped, null, ret); } } else if (target == null) { target = new ArrayList<String>(); } if (patch.mode == Mode.DELETE) { target = new ArrayList<String>(); } else { if (!patch.binary) { int x = 0; for (Hunk hunk : patch.hunks) { x++; try { ret.add(applyHunk(target, hunk, x)); } catch (Exception e) { ret.add(new HunkReport(PatchStatus.Failure, e, 0, 0, x, hunk)); } } } } if (!dryRun) { contextProvider.setData(patch.targetPath, target); } } else { List<String> target; patch.targetFile = computeTargetFile(patch); if (patch.targetFile.exists() && !patch.binary) { target = readFile(patch.targetFile); if (patchCreatesNewFileThatAlreadyExists(patch, target)) { //Check if the patch doesn't need to be applied... for (int x = 0; x < patch.hunks.length; x++) { ret.add(new HunkReport(PatchStatus.Skipped, null, 0, 0, x)); } return new PatchReport(patch.targetPath, patch.binary, PatchStatus.Skipped, null, ret); } } else { target = new ArrayList<String>(); } if (patch.mode == Mode.DELETE) { target = new ArrayList<String>(); } else { if (!patch.binary) { int x = 0; for (Hunk hunk : patch.hunks) { x++; try { ret.add(applyHunk(target, hunk, x)); } catch (Exception e) { ret.add(new HunkReport(PatchStatus.Failure, e, 0, 0, x)); } } } } if (!dryRun) { backup(patch.targetFile); writeFile(patch, target); } } for (HunkReport hunk : ret) { if (hunk.getStatus() == PatchStatus.Failure) { return new PatchReport(patch.targetPath, patch.binary, PatchStatus.Failure, hunk.getFailure(), ret); } } return new PatchReport(patch.targetPath, patch.binary, PatchStatus.Patched, null, ret); } private boolean patchCreatesNewFileThatAlreadyExists(SinglePatch patch, List<String> originalFile) throws PatchException { if (patch.hunks.length != 1) { return false; } Hunk hunk = patch.hunks[0]; if (hunk.baseStart != 0 || hunk.baseCount != 0 || hunk.modifiedStart != 1 || hunk.modifiedCount != originalFile.size()) { return false; } List<String> target = new ArrayList<String>(hunk.modifiedCount); applyHunk(target, hunk, 0); return target.equals(originalFile); } private void backup(File target) throws IOException { if (target.exists()) { copyStreamsCloseAll(new FileOutputStream(computeBackup(target)), new FileInputStream(target)); } } private File computeBackup(File target) { return new File(target.getParentFile(), target.getName() + ".original~"); } private void copyStreamsCloseAll(OutputStream writer, InputStream reader) throws IOException { byte[] buffer = new byte[4096]; int n; while ((n = reader.read(buffer)) != -1) { writer.write(buffer, 0, n); } writer.close(); reader.close(); } private void writeFile(SinglePatch patch, List<String> lines) throws IOException { if (patch.mode == Mode.DELETE) { patch.targetFile.delete(); return; } patch.targetFile.getParentFile().mkdirs(); if (patch.binary) { if (patch.hunks.length == 0) { patch.targetFile.delete(); } else { byte[] content = Base64.decode(patch.hunks[0].lines); copyStreamsCloseAll(new FileOutputStream(patch.targetFile), new ByteArrayInputStream(content)); } } else { PrintWriter w = new PrintWriter( new OutputStreamWriter(new FileOutputStream(patch.targetFile), getEncoding(patch.targetFile))); try { if (lines.size() == 0) { return; } for (String line : lines.subList(0, lines.size() - 1)) { w.println(line); } w.print(lines.get(lines.size() - 1)); if (!patch.noEndingNewline) { w.println(); } } finally { w.close(); } } } private HunkReport applyHunk(List<String> target, Hunk hunk, int hunkID) throws PatchException { int idx = -1; int fuzz = 0; for (; idx == -1 && fuzz <= this.maxFuzz; fuzz++) { idx = findHunkIndex(target, hunk, fuzz, hunkID); if (idx != -1) { break; } } if (idx == -1) { throw new PatchException("Cannot find hunk target"); } return applyHunk(target, hunk, idx, false, fuzz, hunkID); } private int findHunkIndex(List<String> target, Hunk hunk, int fuzz, int hunkID) throws PatchException { int idx = hunk.modifiedStart; // first guess from the hunk range specification if (idx >= lastPatchedLine && applyHunk(target, hunk, idx, true, fuzz, hunkID).getStatus().isSuccess()) { return idx; } else { // try to search for the context for (int i = idx - 1; i >= lastPatchedLine; i--) { if (applyHunk(target, hunk, i, true, fuzz, hunkID).getStatus().isSuccess()) { return i; } } for (int i = idx + 1; i < target.size(); i++) { if (applyHunk(target, hunk, i, true, fuzz, hunkID).getStatus().isSuccess()) { return i; } } } return -1; } /** * @return true if the application succeeded */ private HunkReport applyHunk(List<String> target, Hunk hunk, int idx, boolean dryRun, int fuzz, int hunkID) throws PatchException { int startIdx = idx; idx--; // indices in the target list are 0-based int hunkIdx = -1; for (String hunkLine : hunk.lines) { hunkIdx++; boolean isAddition = isAdditionLine(hunkLine); if (!isAddition) { if (idx >= target.size()) { if (dryRun) { return new HunkReport(PatchStatus.Failure, null, idx, fuzz, hunkID); } else { throw new PatchException("Unapplicable hunk #" + hunkID + " @@ " + startIdx); } } boolean match = similar(target.get(idx), hunkLine.substring(1), hunkLine.charAt(0)); if (!match && fuzz != 0 && !isRemovalLine(hunkLine)) { match = (hunkIdx < fuzz || hunkIdx >= hunk.lines.size() - fuzz ? true : match); } if (!match) { if (dryRun) { return new HunkReport(PatchStatus.Failure, null, idx, fuzz, hunkID); } else { throw new PatchException("Unapplicable hunk #" + hunkID + " @@ " + startIdx); } } } if (dryRun) { if (isAddition) { idx--; } } else { if (isAddition) { target.add(idx, hunkLine.substring(1)); } else if (isRemovalLine(hunkLine)) { target.remove(idx); idx--; } } idx++; } idx++; // indices in the target list are 0-based lastPatchedLine = idx; return new HunkReport((fuzz != 0 ? PatchStatus.Fuzzed : PatchStatus.Patched), null, startIdx, fuzz, hunkID); } private boolean isAdditionLine(String hunkLine) { return hunkLine.charAt(0) == '+'; } private boolean isRemovalLine(String hunkLine) { return hunkLine.charAt(0) == '-'; } private Charset getEncoding(File file) { return Charset.defaultCharset(); } private List<String> readFile(File target) throws IOException { BufferedReader r = new BufferedReader( new InputStreamReader(new FileInputStream(target), getEncoding(target))); try { List<String> lines = new ArrayList<String>(); String line; while ((line = r.readLine()) != null) { lines.add(line); } return lines; } finally { IOUtils.closeQuietly(r); } } private SinglePatch getNextPatch() throws IOException, PatchException { SinglePatch patch = new SinglePatch(); for (;;) { String line = readPatchLine(); if (line == null) { return null; } if (line.startsWith("Index:")) { patch.targetPath = line.substring(6).trim(); } else if (line.startsWith("MIME: application/octet-stream;")) { unreadPatchLine(); readBinaryPatchContent(patch); break; } else if (line.startsWith("--- ")) { unreadPatchLine(); readPatchContent(patch); break; } else if (line.startsWith("*** ")) { unreadPatchLine(); readContextPatchContent(patch); break; } else if (isNormalDiffRange(line)) { unreadPatchLine(); readNormalPatchContent(patch); break; } } return patch; } private boolean isNormalDiffRange(String line) { return normalAddRangePattern.matcher(line).matches() || normalChangeRangePattern.matcher(line).matches() || normalDeleteRangePattern.matcher(line).matches(); } /** * Reads binary diff hunk. */ private void readBinaryPatchContent(SinglePatch patch) throws PatchException, IOException { List<Hunk> hunks = new ArrayList<Hunk>(); Hunk hunk = new Hunk(); for (;;) { String line = readPatchLine(); if (line == null || line.startsWith("Index:") || line.length() == 0) { unreadPatchLine(); break; } if (patch.binary) { hunk.lines.add(line); } else { Matcher m = binaryHeaderPattern.matcher(line); if (m.matches()) { patch.binary = true; int length = Integer.parseInt(m.group(3)); if (length == -1) { break; } hunks.add(hunk); } } } patch.hunks = hunks.toArray(new Hunk[hunks.size()]); } /** * Reads normal diff hunks. */ private void readNormalPatchContent(SinglePatch patch) throws IOException, PatchException { List<Hunk> hunks = new ArrayList<Hunk>(); Hunk hunk = null; Matcher m; for (;;) { String line = readPatchLine(); if (line == null || line.startsWith("Index:")) { unreadPatchLine(); break; } if ((m = normalAddRangePattern.matcher(line)).matches()) { hunk = new Hunk(); hunks.add(hunk); parseNormalRange(hunk, m); } else if ((m = normalChangeRangePattern.matcher(line)).matches()) { hunk = new Hunk(); hunks.add(hunk); parseNormalRange(hunk, m); } else if ((m = normalDeleteRangePattern.matcher(line)).matches()) { hunk = new Hunk(); hunks.add(hunk); parseNormalRange(hunk, m); } else { if (line.startsWith("> ")) { hunk.lines.add("+" + line.substring(2)); } else if (line.startsWith("< ")) { hunk.lines.add("-" + line.substring(2)); } else if (line.startsWith("---")) { // ignore } else { throw new PatchException("Invalid hunk line: " + line); } } } patch.hunks = hunks.toArray(new Hunk[hunks.size()]); } private void parseNormalRange(Hunk hunk, Matcher m) { if (m.pattern() == normalAddRangePattern) { hunk.baseStart = Integer.parseInt(m.group(1)); hunk.baseCount = 0; hunk.modifiedStart = Integer.parseInt(m.group(2)); hunk.modifiedCount = Integer.parseInt(m.group(3)) - hunk.modifiedStart + 1; } else if (m.pattern() == normalDeleteRangePattern) { hunk.baseStart = Integer.parseInt(m.group(1)); hunk.baseCount = Integer.parseInt(m.group(2)) - hunk.baseStart + 1; hunk.modifiedStart = Integer.parseInt(m.group(3)); hunk.modifiedCount = 0; } else { hunk.baseStart = Integer.parseInt(m.group(1)); hunk.baseCount = Integer.parseInt(m.group(2)) - hunk.baseStart + 1; hunk.modifiedStart = Integer.parseInt(m.group(3)); hunk.modifiedCount = Integer.parseInt(m.group(4)) - hunk.modifiedStart + 1; } } /** * Reads context diff hunks. */ private void readContextPatchContent(SinglePatch patch) throws IOException, PatchException { String base = readPatchLine(); if (base == null || !base.startsWith("*** ")) { throw new PatchException("Invalid context diff header: " + base); } String modified = readPatchLine(); if (modified == null || !modified.startsWith("--- ")) { throw new PatchException("Invalid context diff header: " + modified); } if (patch.targetPath == null) { computeTargetPath(base, modified, patch); } List<Hunk> hunks = new ArrayList<Hunk>(); Hunk hunk = null; int lineCount = -1; for (;;) { String line = readPatchLine(); if (line == null || line.length() == 0 || line.startsWith("Index:")) { unreadPatchLine(); break; } else if (line.startsWith("***************")) { hunk = new Hunk(); parseContextRange(hunk, readPatchLine()); hunks.add(hunk); } else if (line.startsWith("--- ")) { lineCount = 0; parseContextRange(hunk, line); hunk.lines.add(line); } else { char c = line.charAt(0); if (c == ' ' || c == '+' || c == '-' || c == '!') { if (lineCount < hunk.modifiedCount) { hunk.lines.add(line); if (lineCount != -1) { lineCount++; } } } else { throw new PatchException("Invalid hunk line: " + line); } } } patch.hunks = hunks.toArray(new Hunk[hunks.size()]); convertContextToUnified(patch); } private void convertContextToUnified(SinglePatch patch) throws PatchException { Hunk[] unifiedHunks = new Hunk[patch.hunks.length]; int idx = 0; for (Hunk hunk : patch.hunks) { unifiedHunks[idx++] = convertContextToUnified(hunk); } patch.hunks = unifiedHunks; } private Hunk convertContextToUnified(Hunk hunk) throws PatchException { Hunk unifiedHunk = new Hunk(); unifiedHunk.baseStart = hunk.baseStart; unifiedHunk.modifiedStart = hunk.modifiedStart; int split = -1; for (int i = 0; i < hunk.lines.size(); i++) { if (hunk.lines.get(i).startsWith("--- ")) { split = i; break; } } if (split == -1) { throw new PatchException("Missing split divider in context patch"); } int baseIdx = 0; int modifiedIdx = split + 1; List<String> unifiedLines = new ArrayList<String>(hunk.lines.size()); for (; baseIdx < split || modifiedIdx < hunk.lines.size();) { String baseLine = baseIdx < split ? hunk.lines.get(baseIdx) : "~"; String modifiedLine = modifiedIdx < hunk.lines.size() ? hunk.lines.get(modifiedIdx) : "~"; if (baseLine.startsWith("- ")) { unifiedLines.add("-" + baseLine.substring(2)); unifiedHunk.baseCount++; baseIdx++; } else if (modifiedLine.startsWith("+ ")) { unifiedLines.add("+" + modifiedLine.substring(2)); unifiedHunk.modifiedCount++; modifiedIdx++; } else if (baseLine.startsWith("! ")) { unifiedLines.add("-" + baseLine.substring(2)); unifiedHunk.baseCount++; baseIdx++; } else if (modifiedLine.startsWith("! ")) { unifiedLines.add("+" + modifiedLine.substring(2)); unifiedHunk.modifiedCount++; modifiedIdx++; } else if (baseLine.startsWith(" ") && modifiedLine.startsWith(" ")) { unifiedLines.add(baseLine.substring(1)); unifiedHunk.baseCount++; unifiedHunk.modifiedCount++; baseIdx++; modifiedIdx++; } else if (baseLine.startsWith(" ")) { unifiedLines.add(baseLine.substring(1)); unifiedHunk.baseCount++; unifiedHunk.modifiedCount++; baseIdx++; } else if (modifiedLine.startsWith(" ")) { unifiedLines.add(modifiedLine.substring(1)); unifiedHunk.baseCount++; unifiedHunk.modifiedCount++; modifiedIdx++; } else { throw new PatchException("Invalid context patch: " + baseLine); } } unifiedHunk.lines = unifiedLines; return unifiedHunk; } /** * Reads unified diff hunks. */ private void readPatchContent(SinglePatch patch) throws IOException, PatchException { String base = readPatchLine(); if (base == null || !base.startsWith("--- ")) { throw new PatchException("Invalid unified diff header: " + base); } String modified = readPatchLine(); if (modified == null || !modified.startsWith("+++ ")) { throw new PatchException("Invalid unified diff header: " + modified); } if (patch.targetPath == null) { computeTargetPath(base, modified, patch); } List<Hunk> hunks = new ArrayList<Hunk>(); Hunk hunk = null; for (;;) { String line = readPatchLine(); if (line == null || line.length() == 0 || line.startsWith("Index:")) { unreadPatchLine(); break; } char c = line.charAt(0); if (c == '@') { hunk = new Hunk(); parseRange(hunk, line); hunks.add(hunk); } else if (c == ' ' || c == '+' || c == '-') { hunk.lines.add(line); } else if (line.equals(Hunk.ENDING_NEWLINE)) { patch.noEndingNewline = true; } else { // first seen in mercurial diffs: be optimistic, this is probably the end of this patch unreadPatchLine(); break; } } patch.hunks = hunks.toArray(new Hunk[hunks.size()]); } private void computeTargetPath(String base, String modified, SinglePatch patch) { base = base.substring("+++ ".length()); modified = modified.substring("--- ".length()); // first seen in mercurial diffs: base and modified paths are different: base starts with "a/" and modified starts with "b/" if ((base.equals("/dev/null") || base.startsWith("a/")) && (modified.equals("/dev/null") || modified.startsWith("b/"))) { if (base.startsWith("a/")) { base = base.substring(2); } if (modified.startsWith("b/")) { modified = modified.substring(2); } } base = untilTab(base).trim(); if (base.equals("/dev/null")) { // "/dev/null" in base indicates a new file patch.targetPath = untilTab(modified).trim(); patch.mode = Mode.ADD; } else { patch.targetPath = base; patch.mode = modified.equals("/dev/null") ? Mode.DELETE : Mode.CHANGE; } } private String untilTab(String base) { int pathEndIdx = base.indexOf('\t'); if (pathEndIdx > 0) { base = base.substring(0, pathEndIdx); } return base; } private void parseRange(Hunk hunk, String range) throws PatchException { Matcher m = unifiedRangePattern.matcher(range); if (!m.matches()) { throw new PatchException("Invalid unified diff range: " + range); } hunk.baseStart = Integer.parseInt(m.group(1)); hunk.baseCount = m.group(2) != null ? Integer.parseInt(m.group(2).substring(1)) : 1; hunk.modifiedStart = Integer.parseInt(m.group(3)); hunk.modifiedCount = m.group(4) != null ? Integer.parseInt(m.group(4).substring(1)) : 1; } private void parseContextRange(Hunk hunk, String range) throws PatchException { if (range.charAt(0) == '*') { Matcher m = baseRangePattern.matcher(range); if (!m.matches()) { throw new PatchException("Invalid context diff range: " + range); } hunk.baseStart = Integer.parseInt(m.group(1)); hunk.baseCount = m.group(2) != null ? Integer.parseInt(m.group(2).substring(1)) : 1; hunk.baseCount -= hunk.baseStart - 1; } else { Matcher m = modifiedRangePattern.matcher(range); if (!m.matches()) { throw new PatchException("Invalid context diff range: " + range); } hunk.modifiedStart = Integer.parseInt(m.group(1)); hunk.modifiedCount = m.group(2) != null ? Integer.parseInt(m.group(2).substring(1)) : 1; hunk.modifiedCount -= hunk.modifiedStart - 1; } } private String readPatchLine() throws IOException { if (patchLineRead) { patchLine = patchReader.readLine(); } else { patchLineRead = true; } return patchLine; } private void unreadPatchLine() { patchLineRead = false; } private void computeContext(List<SinglePatch> patches) { File bestContext = suggestedContext; int bestContextMatched = 0; for (context = suggestedContext; context != null; context = context.getParentFile()) { int patchedFiles = 0; for (SinglePatch patch : patches) { try { applyPatch(patch, true); patchedFiles++; } catch (Exception e) { // patch failed to apply } } if (patchedFiles > bestContextMatched) { bestContextMatched = patchedFiles; bestContext = context; if (patchedFiles == patches.size()) { break; } } } context = bestContext; } private File computeTargetFile(SinglePatch patch) { if (patch.targetPath == null) { patch.targetPath = context.getAbsolutePath(); } if (context.isFile()) { return context; } return new File(context, patch.targetPath); } private static class SinglePatch { //String targetIndex; String targetPath; Hunk[] hunks; //boolean targetMustExist = true; // == false if the patch contains one hunk with just additions ('+' lines) File targetFile; // computed later boolean noEndingNewline; // resulting file should not end with a newline boolean binary; // binary patches contain one encoded Hunk Mode mode; } enum Mode { /** * Update to existing file */ CHANGE, /** * Adding a new file */ ADD, /** * Deleting an existing file */ DELETE } public static enum PatchStatus { Patched(true), Missing(false), Failure(false), Skipped(true), Fuzzed(true); private boolean success; PatchStatus(boolean success) { this.success = success; } public boolean isSuccess() { return success; } } public static final class PatchReport { private String target; private boolean binary; private PatchStatus status; private Throwable failure; private List<HunkReport> hunks; PatchReport(String target, boolean binary, PatchStatus status, Throwable failure, List<HunkReport> hunks) { this.target = target; this.binary = binary; this.status = status; this.failure = failure; this.hunks = hunks; } public String getTarget() { return target; } public boolean isBinary() { return binary; } public PatchStatus getStatus() { return status; } public Throwable getFailure() { return failure; } public List<HunkReport> getHunks() { return hunks; } } public static interface IContextProvider { public List<String> getData(String target); public void setData(String target, List<String> data); } public static class HunkReport { private PatchStatus status; private Throwable failure; private int index; private int fuzz; private int hunkID; public Hunk hunk; public HunkReport(PatchStatus status, Throwable failure, int index, int fuzz, int hunkID) { this.status = status; this.failure = failure; this.index = index; this.fuzz = fuzz; this.hunkID = hunkID; } public HunkReport(PatchStatus status, Throwable failure, int index, int fuzz, int hunkID, Hunk hunk) { this(status, failure, index, fuzz, hunkID); this.hunk = hunk; } public PatchStatus getStatus() { return status; } public Throwable getFailure() { return failure; } public int getIndex() { return index; } public int getFuzz() { return fuzz; } public int getHunkID() { return hunkID; } } private boolean similar(String target, String hunk, char lineType) { if (c14nAccess) { if (c14nWhitespace) { target = target.replaceAll("[\t| ]+", " "); hunk = hunk.replaceAll("[\t| ]+", " "); } String[] t = target.split(" "); String[] h = hunk.split(" "); if (t.length != h.length) { return false; } for (int x = 0; x < t.length; x++) { if (isAccess(t[x]) && isAccess(h[x])) { continue; } else { if (!t[x].equals(h[x])) { return false; } } } return true; } if (c14nWhitespace) { return target.replaceAll("[\t| ]+", " ").equals(hunk.replaceAll("[\t| ]+", " ")); } else { return target.equals(hunk); } } private boolean isAccess(String data) { return data.equalsIgnoreCase("public") || data.equalsIgnoreCase("private") || data.equalsIgnoreCase("protected"); } }