RawTextComparator.java

  1. /*
  2.  * Copyright (C) 2009-2010, Google Inc.
  3.  * Copyright (C) 2008-2009, Johannes E. Schindelin <johannes.schindelin@gmx.de> and others
  4.  *
  5.  * This program and the accompanying materials are made available under the
  6.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  7.  * https://www.eclipse.org/org/documents/edl-v10.php.
  8.  *
  9.  * SPDX-License-Identifier: BSD-3-Clause
  10.  */

  11. package org.eclipse.jgit.diff;

  12. import static org.eclipse.jgit.util.RawCharUtil.isWhitespace;
  13. import static org.eclipse.jgit.util.RawCharUtil.trimLeadingWhitespace;
  14. import static org.eclipse.jgit.util.RawCharUtil.trimTrailingWhitespace;

  15. import org.eclipse.jgit.util.IntList;

  16. /**
  17.  * Equivalence function for {@link org.eclipse.jgit.diff.RawText}.
  18.  */
  19. public abstract class RawTextComparator extends SequenceComparator<RawText> {
  20.     /** No special treatment. */
  21.     public static final RawTextComparator DEFAULT = new RawTextComparator() {
  22.         @Override
  23.         public boolean equals(RawText a, int ai, RawText b, int bi) {
  24.             ai++;
  25.             bi++;

  26.             int as = a.lines.get(ai);
  27.             int bs = b.lines.get(bi);
  28.             final int ae = a.lines.get(ai + 1);
  29.             final int be = b.lines.get(bi + 1);

  30.             if (ae - as != be - bs)
  31.                 return false;

  32.             while (as < ae) {
  33.                 if (a.content[as++] != b.content[bs++])
  34.                     return false;
  35.             }
  36.             return true;
  37.         }

  38.         @Override
  39.         protected int hashRegion(byte[] raw, int ptr, int end) {
  40.             int hash = 5381;
  41.             for (; ptr < end; ptr++)
  42.                 hash = ((hash << 5) + hash) + (raw[ptr] & 0xff);
  43.             return hash;
  44.         }
  45.     };

  46.     /** Ignores all whitespace. */
  47.     public static final RawTextComparator WS_IGNORE_ALL = new RawTextComparator() {
  48.         @Override
  49.         public boolean equals(RawText a, int ai, RawText b, int bi) {
  50.             ai++;
  51.             bi++;

  52.             int as = a.lines.get(ai);
  53.             int bs = b.lines.get(bi);
  54.             int ae = a.lines.get(ai + 1);
  55.             int be = b.lines.get(bi + 1);

  56.             ae = trimTrailingWhitespace(a.content, as, ae);
  57.             be = trimTrailingWhitespace(b.content, bs, be);

  58.             while (as < ae && bs < be) {
  59.                 byte ac = a.content[as];
  60.                 byte bc = b.content[bs];

  61.                 while (as < ae - 1 && isWhitespace(ac)) {
  62.                     as++;
  63.                     ac = a.content[as];
  64.                 }

  65.                 while (bs < be - 1 && isWhitespace(bc)) {
  66.                     bs++;
  67.                     bc = b.content[bs];
  68.                 }

  69.                 if (ac != bc)
  70.                     return false;

  71.                 as++;
  72.                 bs++;
  73.             }

  74.             return as == ae && bs == be;
  75.         }

  76.         @Override
  77.         protected int hashRegion(byte[] raw, int ptr, int end) {
  78.             int hash = 5381;
  79.             for (; ptr < end; ptr++) {
  80.                 byte c = raw[ptr];
  81.                 if (!isWhitespace(c))
  82.                     hash = ((hash << 5) + hash) + (c & 0xff);
  83.             }
  84.             return hash;
  85.         }
  86.     };

  87.     /**
  88.      * Ignore leading whitespace.
  89.      **/
  90.     public static final RawTextComparator WS_IGNORE_LEADING = new RawTextComparator() {
  91.         @Override
  92.         public boolean equals(RawText a, int ai, RawText b, int bi) {
  93.             ai++;
  94.             bi++;

  95.             int as = a.lines.get(ai);
  96.             int bs = b.lines.get(bi);
  97.             int ae = a.lines.get(ai + 1);
  98.             int be = b.lines.get(bi + 1);

  99.             as = trimLeadingWhitespace(a.content, as, ae);
  100.             bs = trimLeadingWhitespace(b.content, bs, be);

  101.             if (ae - as != be - bs)
  102.                 return false;

  103.             while (as < ae) {
  104.                 if (a.content[as++] != b.content[bs++])
  105.                     return false;
  106.             }
  107.             return true;
  108.         }

  109.         @Override
  110.         protected int hashRegion(byte[] raw, int ptr, int end) {
  111.             int hash = 5381;
  112.             ptr = trimLeadingWhitespace(raw, ptr, end);
  113.             for (; ptr < end; ptr++)
  114.                 hash = ((hash << 5) + hash) + (raw[ptr] & 0xff);
  115.             return hash;
  116.         }
  117.     };

  118.     /** Ignores trailing whitespace. */
  119.     public static final RawTextComparator WS_IGNORE_TRAILING = new RawTextComparator() {
  120.         @Override
  121.         public boolean equals(RawText a, int ai, RawText b, int bi) {
  122.             ai++;
  123.             bi++;

  124.             int as = a.lines.get(ai);
  125.             int bs = b.lines.get(bi);
  126.             int ae = a.lines.get(ai + 1);
  127.             int be = b.lines.get(bi + 1);

  128.             ae = trimTrailingWhitespace(a.content, as, ae);
  129.             be = trimTrailingWhitespace(b.content, bs, be);

  130.             if (ae - as != be - bs)
  131.                 return false;

  132.             while (as < ae) {
  133.                 if (a.content[as++] != b.content[bs++])
  134.                     return false;
  135.             }
  136.             return true;
  137.         }

  138.         @Override
  139.         protected int hashRegion(byte[] raw, int ptr, int end) {
  140.             int hash = 5381;
  141.             end = trimTrailingWhitespace(raw, ptr, end);
  142.             for (; ptr < end; ptr++)
  143.                 hash = ((hash << 5) + hash) + (raw[ptr] & 0xff);
  144.             return hash;
  145.         }
  146.     };

  147.     /** Ignores whitespace occurring between non-whitespace characters. */
  148.     public static final RawTextComparator WS_IGNORE_CHANGE = new RawTextComparator() {
  149.         @Override
  150.         public boolean equals(RawText a, int ai, RawText b, int bi) {
  151.             ai++;
  152.             bi++;

  153.             int as = a.lines.get(ai);
  154.             int bs = b.lines.get(bi);
  155.             int ae = a.lines.get(ai + 1);
  156.             int be = b.lines.get(bi + 1);

  157.             ae = trimTrailingWhitespace(a.content, as, ae);
  158.             be = trimTrailingWhitespace(b.content, bs, be);

  159.             while (as < ae && bs < be) {
  160.                 byte ac = a.content[as++];
  161.                 byte bc = b.content[bs++];

  162.                 if (isWhitespace(ac) && isWhitespace(bc)) {
  163.                     as = trimLeadingWhitespace(a.content, as, ae);
  164.                     bs = trimLeadingWhitespace(b.content, bs, be);
  165.                 } else if (ac != bc) {
  166.                     return false;
  167.                 }
  168.             }
  169.             return as == ae && bs == be;
  170.         }

  171.         @Override
  172.         protected int hashRegion(byte[] raw, int ptr, int end) {
  173.             int hash = 5381;
  174.             end = trimTrailingWhitespace(raw, ptr, end);
  175.             while (ptr < end) {
  176.                 byte c = raw[ptr++];
  177.                 if (isWhitespace(c)) {
  178.                     ptr = trimLeadingWhitespace(raw, ptr, end);
  179.                     c = ' ';
  180.                 }
  181.                 hash = ((hash << 5) + hash) + (c & 0xff);
  182.             }
  183.             return hash;
  184.         }
  185.     };

  186.     @Override
  187.     public int hash(RawText seq, int lno) {
  188.         final int begin = seq.lines.get(lno + 1);
  189.         final int end = seq.lines.get(lno + 2);
  190.         return hashRegion(seq.content, begin, end);
  191.     }

  192.     /** {@inheritDoc} */
  193.     @Override
  194.     public Edit reduceCommonStartEnd(RawText a, RawText b, Edit e) {
  195.         // This is a faster exact match based form that tries to improve
  196.         // performance for the common case of the header and trailer of
  197.         // a text file not changing at all. After this fast path we use
  198.         // the slower path based on the super class' using equals() to
  199.         // allow for whitespace ignore modes to still work.

  200.         if (e.beginA == e.endA || e.beginB == e.endB)
  201.             return e;

  202.         byte[] aRaw = a.content;
  203.         byte[] bRaw = b.content;

  204.         int aPtr = a.lines.get(e.beginA + 1);
  205.         int bPtr = a.lines.get(e.beginB + 1);

  206.         int aEnd = a.lines.get(e.endA + 1);
  207.         int bEnd = b.lines.get(e.endB + 1);

  208.         // This can never happen, but the JIT doesn't know that. If we
  209.         // define this assertion before the tight while loops below it
  210.         // should be able to skip the array bound checks on access.
  211.         //
  212.         if (aPtr < 0 || bPtr < 0 || aEnd > aRaw.length || bEnd > bRaw.length)
  213.             throw new ArrayIndexOutOfBoundsException();

  214.         while (aPtr < aEnd && bPtr < bEnd && aRaw[aPtr] == bRaw[bPtr]) {
  215.             aPtr++;
  216.             bPtr++;
  217.         }

  218.         while (aPtr < aEnd && bPtr < bEnd && aRaw[aEnd - 1] == bRaw[bEnd - 1]) {
  219.             aEnd--;
  220.             bEnd--;
  221.         }

  222.         e.beginA = findForwardLine(a.lines, e.beginA, aPtr);
  223.         e.beginB = findForwardLine(b.lines, e.beginB, bPtr);

  224.         e.endA = findReverseLine(a.lines, e.endA, aEnd);

  225.         final boolean partialA = aEnd < a.lines.get(e.endA + 1);
  226.         if (partialA)
  227.             bEnd += a.lines.get(e.endA + 1) - aEnd;

  228.         e.endB = findReverseLine(b.lines, e.endB, bEnd);

  229.         if (!partialA && bEnd < b.lines.get(e.endB + 1))
  230.             e.endA++;

  231.         return super.reduceCommonStartEnd(a, b, e);
  232.     }

  233.     private static int findForwardLine(IntList lines, int idx, int ptr) {
  234.         final int end = lines.size() - 2;
  235.         while (idx < end && lines.get(idx + 2) < ptr)
  236.             idx++;
  237.         return idx;
  238.     }

  239.     private static int findReverseLine(IntList lines, int idx, int ptr) {
  240.         while (0 < idx && ptr <= lines.get(idx))
  241.             idx--;
  242.         return idx;
  243.     }

  244.     /**
  245.      * Compute a hash code for a region.
  246.      *
  247.      * @param raw
  248.      *            the raw file content.
  249.      * @param ptr
  250.      *            first byte of the region to hash.
  251.      * @param end
  252.      *            1 past the last byte of the region.
  253.      * @return hash code for the region <code>[ptr, end)</code> of raw.
  254.      */
  255.     protected abstract int hashRegion(byte[] raw, int ptr, int end);
  256. }