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

framework.Diff Maven / Gradle / Ivy

package framework;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import difflib.Chunk;
import difflib.Delta;
import difflib.myers.Equalizer;
import difflib.myers.MyersDiff;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 * Diff
 * 
 * @param  Diff element type
 */
public class Diff {
    @SuppressWarnings("javadoc")
    public enum Type {
        EQUAL,
        INSERT,
        DELETE,
        CHANGE,
        SKIP;
        @Override
        public String toString() {
            return name().toLowerCase(Locale.ENGLISH);
        }
    }

    /**
     * Diff type
     */
    public Type type;
    /**
     * before index
     */
    public Optional beforeIndex;
    /**
     * before
     */
    public Optional before;
    /**
     * after index
     */
    public Optional afterIndex;
    /**
     * after
     */
    public Optional after;

    /**
     * @return before index as text
     */
    public String getBeforeIndexText() {
        return type == Type.SKIP ? getBeforeText() : beforeIndex.map(String::valueOf).orElse("");
    }

    /**
     * @return before as text
     */
    public String getBeforeText() {
        return before.map(String::valueOf).orElse("");
    }

    /**
     * @return after index as text
     */
    public String getAfterIndexText() {
        return type == Type.SKIP ? getAfterText() : afterIndex.map(String::valueOf).orElse("");
    }

    /**
     * @return after as text
     */
    public String getAfterText() {
        return after.map(String::valueOf).orElse("");
    }

    /**
     * default equalizer
     * 
     * @param  element type
     * @return equalizer
     */
    public static  Equalizer DEFAULT() {
        return Object::equals;
    }

    /**
     * ignore space equalizer
     */
    public static final Equalizer IGNORE_SPACE = (i, j) -> i.replaceAll("\\s{2,}", " ").equals(j.replaceAll("\\s{2,}", " "));

    /**
     * HTML escape editor
     */
    public static final Consumer> ESCAPE = d -> {
        d.before = d.before.map(Tool::htmlEscape);
        d.after = d.after.map(Tool::htmlEscape);
    };

    /**
     * @param start start index
     * @param end end index
     * @param tag tag name
     * @return insert tag function
     */
    static Function insertTag(int start, int end, String tag) {
        return s -> s.substring(0, start) + "<" + tag + ">" + s.substring(start, end) + "" + s.substring(end);
    }

    /**
     * @param tag letter decoration tag(ex: b)
     * @param limit mark limit
     * @return editor
     */
    public static Consumer> INLINE(String tag, int limit) {
        return d -> {
            d.before = d.before.map(Tool::htmlEscape);
            d.after = d.after.map(Tool::htmlEscape);
            if (d.type != Type.CHANGE) {
                return;
            }
            List> deltas = new MyersDiff(DEFAULT())
                    .diff(d.before.map(Tool::toCharacterArray).orElse(new Character[] {}), d.after.map(Tool::toCharacterArray).orElse(new Character[] {}))
                    .getDeltas();
            if (deltas.isEmpty()) {
                return;
            }
            Function, Integer> getEnd = i -> i.getPosition() + i.getLines().size();
            if (deltas.size() > limit) {
                Delta top = deltas.get(0);
                Delta last = deltas.get(deltas.size() - 1);
                {
                    int start = top.getOriginal().getPosition();
                    int end = Tool.val(last.getOriginal(), getEnd);
                    if (start < end) {
                        d.before = d.before.map(insertTag(start, end, tag));
                    }
                }
                {
                    int start = top.getRevised().getPosition();
                    int end = Tool.val(last.getRevised(), getEnd);
                    if (start < end) {
                        d.after = d.after.map(insertTag(start, end, tag));
                    }
                }
            } else {
                Collections.reverse(deltas);
                for (Delta delta : deltas) {
                    {
                        int start = delta.getOriginal().getPosition();
                        int end = Tool.val(delta.getOriginal(), getEnd);
                        if (start < end) {
                            d.before = d.before.map(insertTag(start, end, tag));
                        }
                    }
                    {
                        int start = delta.getRevised().getPosition();
                        int end = Tool.val(delta.getRevised(), getEnd);
                        if (start < end) {
                            d.after = d.after.map(insertTag(start, end, tag));
                        }
                    }
                }
            }
        };
    }

    /**
     * @param size tab size
     * @return editor
     */
    public static Consumer> TAB(int size) {
        return d -> {
            String tab = Collections.nCopies(size, " ").stream().collect(Collectors.joining());
            Function f = s -> s.replace("\t", tab).replaceAll("[ ]{2}", "  ");
            d.before = d.before.map(f);
            d.after = d.after.map(f);
        };
    }

    /**
     * @param type Diff element type
     * @param beforeIndex before line number
     * @param before before line text
     * @param afterIndex after line number
     * @param after after line text
     */
    @SuppressFBWarnings({ "URF_UNREAD_FIELD", "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD" })
    public Diff(Type type, Optional beforeIndex, Optional before, Optional afterIndex, Optional after) {
        this.type = type;
        this.beforeIndex = beforeIndex;
        this.before = before;
        this.afterIndex = afterIndex;
        this.after = after;
    }

    /**
     * @param  Diff type
     * @param before before
     * @param after after
     * @param equalizer equalizer(default Diff.DEFAULT())
     * @param editor editor(default null)
     * @return diff
     */
    public static  List> diff(T[] before, T[] after, Equalizer equalizer, Consumer> editor) {
        List> list = new ArrayList<>();
        int currentB = 0, currentA = 0;
        for (Delta delta : new MyersDiff(equalizer).diff(before, after).getDeltas()) {
            Chunk b = delta.getOriginal();
            Chunk a = delta.getRevised();
            List bLines = b.getLines();
            List aLines = a.getLines();
            for (int i = 0, startB = b.getPosition(), startA = a.getPosition(), maxB = b.size(), maxA = a.size(), endB = startB + maxB, endA = startA
                    + maxA; startB < endB || startA < endA; i++, startB++, startA++) {
                for (; currentB < before.length && currentA < after.length && currentB < startB && currentA < startA; currentB++, currentA++) {
                    list.add(Tool.peek(new Diff<>(Type.EQUAL, Optional.of(currentB + 1), Optional.of(before[currentB]), Optional.of(currentA + 1),
                            Optional.of(after[currentA])), editor));
                }
                Type type = Type.valueOf(delta.getType().name());
                if (type == Type.CHANGE) {
                    if (i >= maxB) {
                        type = Type.INSERT;
                    } else if (i >= maxA) {
                        type = Type.DELETE;
                    }
                }
                boolean hasB = i < maxB;
                boolean hasA = i < maxA;
                list.add(Tool.peek(new Diff<>(type, Optional.of(currentB + 1).filter(n -> hasB), Tool.of(hasB ? bLines.get(i) : null),
                        Optional.of(currentA + 1).filter(n -> hasA), Tool.of(hasA ? aLines.get(i) : null)), editor));
                if (currentB < endB) {
                    currentB++;
                }
                if (currentA < endA) {
                    currentA++;
                }
            }
        }
        for (int endB = before.length, endA = after.length, end = Math.max(endB, endA); currentB < end; currentB++, currentA++) {
            boolean hasB = currentB < endB;
            boolean hasA = currentA < endA;
            Type type = hasB && hasA ? Type.EQUAL : hasB ? Type.DELETE : Type.INSERT;
            list.add(Tool.peek(new Diff<>(type, Optional.of(currentB + 1).filter(n -> hasB), Tool.of(hasB ? before[currentB] : null),
                    Optional.of(currentA + 1).filter(n -> hasA), Tool.of(hasA ? after[currentA] : null)), editor));
        }
        return list;
    }

    /**
     * @param args not use
     */
    public static void main(String[] args) {
        System.out.print(
                Tool.json(Diff.diff(Tool.htmlEscape("  ").chars().mapToObj(i -> (char) i)
                        .toArray(Character[]::new), Tool.htmlEscape("   -->").chars().mapToObj(i -> (char) i).toArray(Character[]::new), DEFAULT(), null)));
    }

    /**
     * @param  element type
     * @param diffs target
     * @param lines rest lines(no compact if less then 0)
     * @param skipContent skip line content
     * @return compacted diffs
     */
    public static  List> compact(List> diffs, int lines, T skipContent) {
        if (lines <= 0) {
            return diffs;
        }
        SortedSet picks = new TreeSet<>();
        int max = diffs.size();
        int i = 0;
        for (Diff diff : diffs) {
            if (diff.type != Type.EQUAL) {
                for (int j = Math.max(0, i - lines), j2 = Math.min(i + lines + 1, max); j < j2; j++) {
                    picks.add(j);
                }
            }
            i++;
        }
        List> result = new ArrayList<>();
        int pick0 = -1;
        for (int pick : picks) {
            if (pick0 + 1 != pick) {
                result.add(new Diff<>(Type.SKIP, Optional.empty(), Tool.of(skipContent), Optional.empty(), Tool.of(skipContent)));
            }
            pick0 = pick;
            result.add(diffs.get(pick));
        }
        return result;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy