Package net.minecraftforge.gradle.patching

Source Code of net.minecraftforge.gradle.patching.ContextualPatch$PatchReport

/*
* 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 net.minecraftforge.gradle.patching;

import com.cloudbees.diff.Hunk;
import com.cloudbees.diff.PatchException;
import org.apache.commons.io.IOUtils;

import java.io.*;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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

Related Classes of net.minecraftforge.gradle.patching.ContextualPatch$PatchReport

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.