All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.morimekta.diff.DiffLines Maven / Gradle / Ivy

There is a newer version: 3.1.1
Show newest version
package net.morimekta.diff;

import java.util.Collections;
import java.util.LinkedList;

/**
 * Make diff based on lines.
 */
public class DiffLines extends DiffBase {
    private final LinkedList changeList;

    public DiffLines(String text1, String text2) {
        this(text1, text2, DiffOptions.defaults());
    }
    public DiffLines(String text1, String text2, DiffOptions options) {
        super(options, getDeadline(options));

        this.changeList = makeLineDiff(text1, text2);
    }

    /**
     * @return The full diff including unchanged lines.
     */
    public String fullDiff() {
        StringBuilder builder = new StringBuilder();
        for (Change ch : getChangeList()) {
            builder.append(ch.patchLine())
                   .append("\n");
        }
        return builder.toString();
    }

    /**
     * @return Make a diff description usable by unix patch
     *         program.
     */
    public String patch() {
        StringBuilder builder = new StringBuilder();
        int src_pos = 1, trg_pos = 1;
        LinkedList changes = new LinkedList<>(getChangeList());
        while (!changes.isEmpty()) {
            if (changes.peekFirst().operation == Operation.EQUAL) {
                changes.pollFirst();
                ++src_pos;
                ++trg_pos;
                continue;
            }
            LinkedList upcoming = new LinkedList<>();

            int ins = 0;
            int rms = 0;
            while (!changes.isEmpty()) {
                if (changes.peekFirst().operation == Operation.EQUAL) {
                    break;
                }
                Change ch = changes.pollFirst();
                upcoming.add(ch);
                if (ch.operation == Operation.INSERT) {
                    ++ins;
                } else {
                    ++rms;
                }
            }

            builder.append("@@ -")
                   .append(src_pos)
                   .append(',')
                   .append(rms)
                   .append(" +")
                   .append(trg_pos)
                   .append(',')
                   .append(ins)
                   .append(" @@\n");

            for (Change ch : upcoming) {
                builder.append(ch.patchLine())
                       .append("\n");
                if (ch.operation == Operation.INSERT) {
                    ++trg_pos;
                } else {
                    ++src_pos;
                }
            }
        }

        return builder.toString();
    }

    private LinkedList makeLineDiff(String source, String target) {
        LinkedList src_lines = new LinkedList<>();
        LinkedList trg_lines = new LinkedList<>();
        Collections.addAll(src_lines, source.split("\\r?\\n"));
        Collections.addAll(trg_lines, target.split("\\r?\\n"));

        LinkedList beg = new LinkedList<>();
        LinkedList end = new LinkedList<>();
        while (true) {
            // This checks if the last change is a pure insert or delete.
            if (src_lines.isEmpty() || trg_lines.isEmpty()) {
                break;
            }

            // No change on all top lines -> beg (EQ).
            String src_first = src_lines.peekFirst();
            String trg_first = trg_lines.peekFirst();

            if (src_first.equals(trg_first)) {
                beg.add(new Change(Operation.EQUAL, src_lines.pollFirst()));
                trg_lines.pollFirst();
                continue;
            }

            // No change in bottom lines -> end (EQ)
            String src_last = src_lines.peekLast();
            String trg_last = trg_lines.peekLast();
            if (src_last.equals(trg_last)) {
                end.add(0, new Change(Operation.EQUAL, src_lines.pollLast()));
                trg_lines.pollLast();
                continue;
            }

            // a differing line.
            int up_next = src_lines.indexOf(trg_first);
            int down_next = trg_lines.indexOf(src_first);
            if (up_next == -1 && down_next >= 0) {
                // Added line.
                beg.add(new Change(Operation.INSERT, trg_lines.pollFirst()));
                continue;
            }
            if (down_next == -1 && up_next >= 0) {
                // Removed line.
                beg.add(new Change(Operation.DELETE, src_lines.pollFirst()));
                continue;
            }

            if (up_next >= 0 && down_next >= 0) {
                // Check number of lines moved **UP** (top in target found in source)
                int up_move = 1;
                while (up_next + up_move < src_lines.size() &&
                       up_move < trg_lines.size()) {
                    if (src_lines.get(up_next + up_move)
                              .equals(trg_lines.get(up_move))) {
                        ++up_move;
                    } else {
                        break;
                    }
                }

                // Check number of lines moved **DOWN** (top in source found in target)
                int down_move = 1;
                while (down_next + down_move < trg_lines.size() &&
                       down_move < src_lines.size()) {
                    if (trg_lines.get(down_next + down_move)
                              .equals(src_lines.get(down_move))) {
                        ++down_move;
                    } else {
                        break;
                    }
                }

                // First choose the shorter consecutive diff.
                if (up_move > down_move) {
                    up_move = 0;
                } else if (up_move < down_move) {
                    down_move = 0;
                } else {
                    // Then the closest diff.
                    if (up_next > down_next){
                        up_move = 0;
                    } else {
                        down_move = 0;
                    }
                }

                if (down_move > 0) {
                    while (down_move-- > 0) {
                        beg.add(new Change(Operation.DELETE, src_lines.pollFirst()));
                    }
                } else {
                    while (up_move-- > 0) {
                        beg.add(new Change(Operation.INSERT, trg_lines.pollFirst()));
                    }
                }
                continue;
            }

            // added AND removed, aka change.
            beg.add(new Change(Operation.DELETE, src_lines.pollFirst()));
            beg.add(new Change(Operation.INSERT, trg_lines.pollFirst()));
        }

        while (!src_lines.isEmpty()) {
            beg.add(new Change(Operation.DELETE, src_lines.pollFirst()));
        }
        while (!trg_lines.isEmpty()) {
            beg.add(new Change(Operation.INSERT, trg_lines.pollFirst()));
        }

        LinkedList changes = new LinkedList<>();
        changes.addAll(beg);
        changes.addAll(end);

        // Sort diff lines so that and continuous change of insert and delete becomes:
        // -- all deleted
        // -- all inserts
        LinkedList result = new LinkedList<>();
        // keep inserts for after deleted.
        LinkedList inserts = new LinkedList<>();
        for (Change change : changes) {
            switch (change.operation) {
                case EQUAL:
                    result.addAll(inserts);
                    inserts.clear();
                    result.add(change);
                    break;
                case DELETE:
                    result.add(change);
                    break;
                case INSERT:
                    inserts.add(change);
                    break;
            }
        }
        result.addAll(inserts);

        return result;
    }

    /**
     * @return The list of change and non-change entries, one per line.
     */
    @Override
    public LinkedList getChangeList() {
        return changeList;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy