|
1 | 1 | package com.softwaremill.diffx.instances
|
2 | 2 |
|
3 |
| -import com.softwaremill.diffx.Matching.MatchingResults |
4 | 3 | import com.softwaremill.diffx.ObjectMatcher.{IterableEntry, MapEntry}
|
5 | 4 | import com.softwaremill.diffx._
|
| 5 | +import com.softwaremill.diffx.instances.DiffForIterable._ |
| 6 | +import com.softwaremill.diffx.instances.internal.MatchResult |
6 | 7 |
|
7 | 8 | import scala.annotation.tailrec
|
8 |
| -import scala.collection.immutable.{ListMap, ListSet} |
| 9 | +import scala.collection.immutable.ListMap |
9 | 10 |
|
10 | 11 | private[diffx] class DiffForIterable[T, C[W] <: Iterable[W]](
|
11 | 12 | dt: Diff[T],
|
12 | 13 | matcher: ObjectMatcher[IterableEntry[T]]
|
13 | 14 | ) extends Diff[C[T]] {
|
14 | 15 | override def apply(left: C[T], right: C[T], context: DiffContext): DiffResult = nullGuard(left, right) {
|
15 | 16 | (left, right) =>
|
16 |
| - val keys = Range(0, Math.max(left.size, right.size)) |
17 |
| - |
18 |
| - val leftAsMap = left.toList.lift |
19 |
| - val rightAsMap = right.toList.lift |
20 |
| - val lEntries = keys.map(i => i -> leftAsMap(i)).collect { case (k, Some(v)) => MapEntry(k, v) }.toList |
21 |
| - val rEntries = keys.map(i => i -> rightAsMap(i)).collect { case (k, Some(v)) => MapEntry(k, v) }.toList |
22 |
| - |
23 | 17 | val adjustedMatcher = context.getMatcherOverride[IterableEntry[T]].getOrElse(matcher)
|
24 |
| - val MatchingResults(unMatchedLeftInstances, unMatchedRightInstances, matchedInstances) = |
25 |
| - matchPairs(lEntries, rEntries, adjustedMatcher) |
26 |
| - val leftDiffs = unMatchedLeftInstances |
27 |
| - .diff(unMatchedRightInstances) |
28 |
| - .collectFirst { case MapEntry(k, v) => k -> DiffResultAdditional(v) } |
29 |
| - .toList |
30 |
| - val rightDiffs = unMatchedRightInstances |
31 |
| - .diff(unMatchedLeftInstances) |
32 |
| - .collectFirst { case MapEntry(k, v) => k -> DiffResultMissing(v) } |
33 |
| - .toList |
34 |
| - val matchedDiffs = matchedInstances.map { case (l, r) => l.key -> dt(l.value, r.value, context) }.toList |
| 18 | + val leftWithIndex = left.zipWithIndex.map { case (lv, i) => MapEntry(i, lv) }.toList |
| 19 | + val rightWithIndex = right.zipWithIndex.map { case (rv, i) => MapEntry(i, rv) }.toList |
35 | 20 |
|
36 |
| - val diffs = ListMap((matchedDiffs ++ leftDiffs ++ rightDiffs).map { case (k, v) => k.toString -> v }: _*) |
| 21 | + val matches = matchPairs(leftWithIndex, rightWithIndex, adjustedMatcher, List.empty, context) |
| 22 | + val sortedDiffs = matches.sorted.map { |
| 23 | + case MatchResult.UnmatchedLeft(v) => DiffResultAdditional(v.value) |
| 24 | + case MatchResult.UnmatchedRight(v) => DiffResultMissing(v.value) |
| 25 | + case MatchResult.Matched(l, r) => dt.apply(l.value, r.value, context) |
| 26 | + } |
| 27 | + val reindexed = sortedDiffs.zipWithIndex.map(_.swap) |
| 28 | + val diffs = ListMap(reindexed.map { case (k, v) => k.toString -> v }: _*) |
37 | 29 | DiffResultObject("List", diffs)
|
38 | 30 | }
|
39 | 31 |
|
| 32 | + @tailrec |
40 | 33 | private def matchPairs(
|
41 | 34 | left: List[IterableEntry[T]],
|
42 | 35 | right: List[IterableEntry[T]],
|
43 |
| - matcher: ObjectMatcher[IterableEntry[T]] |
44 |
| - ): MatchingResults[IterableEntry[T]] = { |
45 |
| - @tailrec |
46 |
| - def loop( |
47 |
| - left: List[IterableEntry[T]], |
48 |
| - right: List[IterableEntry[T]], |
49 |
| - matches: List[(IterableEntry[T], IterableEntry[T])], |
50 |
| - leftUnmatched: List[IterableEntry[T]] |
51 |
| - ): MatchingResults[IterableEntry[T]] = { |
52 |
| - left match { |
53 |
| - case lHead :: tail => |
54 |
| - val maybeMatch = right.collectFirst { |
55 |
| - case r if matcher.isSameObject(lHead, r) => lHead -> r |
56 |
| - } |
57 |
| - maybeMatch match { |
58 |
| - case Some(m @ (_, rm)) => |
59 |
| - loop(tail, right.filterNot(r => r.key == rm.key), matches :+ m, leftUnmatched) |
60 |
| - case None => loop(tail, right, matches, leftUnmatched :+ lHead) |
61 |
| - } |
62 |
| - case Nil => MatchingResults(ListSet(leftUnmatched: _*), ListSet(right: _*), ListSet(matches: _*)) |
63 |
| - } |
| 36 | + matcher: ObjectMatcher[IterableEntry[T]], |
| 37 | + matched: List[MatchResult[IterableEntry[T]]], |
| 38 | + context: DiffContext |
| 39 | + ): List[MatchResult[IterableEntry[T]]] = { |
| 40 | + right match { |
| 41 | + case ::(rHead, tailRight) => |
| 42 | + val maybeMatched = left |
| 43 | + .collect { case l if matcher.isSameObject(rHead, l) => l -> rHead } |
| 44 | + .sortBy { case (l, r) => !dt.apply(l.value, r.value, context).isIdentical } |
| 45 | + .headOption |
| 46 | + maybeMatched match { |
| 47 | + case Some((lm, rm)) => |
| 48 | + matchPairs( |
| 49 | + left.filterNot(l => l.key == lm.key), |
| 50 | + tailRight, |
| 51 | + matcher, |
| 52 | + matched :+ MatchResult.Matched(lm, rm), |
| 53 | + context |
| 54 | + ) |
| 55 | + case None => matchPairs(left, tailRight, matcher, matched :+ MatchResult.UnmatchedRight(rHead), context) |
| 56 | + } |
| 57 | + case Nil => matched ++ left.map(l => MatchResult.UnmatchedLeft(l)) |
64 | 58 | }
|
65 |
| - loop(left, right, List.empty, List.empty) |
66 | 59 | }
|
67 | 60 | }
|
| 61 | + |
| 62 | +object DiffForIterable { |
| 63 | + implicit def iterableEntryOrdering[T]: Ordering[IterableEntry[T]] = Ordering.by(_.key) |
| 64 | + implicit def diffResultOrdering[T]: Ordering[MatchResult[IterableEntry[T]]] = |
| 65 | + new Ordering[MatchResult[IterableEntry[T]]] { |
| 66 | + override def compare(x: MatchResult[IterableEntry[T]], y: MatchResult[IterableEntry[T]]): Int = { |
| 67 | + (x, y) match { |
| 68 | + case (ur: MatchResult.UnmatchedRight[IterableEntry[T]], m: MatchResult.Matched[IterableEntry[T]]) => |
| 69 | + iterableEntryOrdering.compare(ur.v, m.r) |
| 70 | + case (_: MatchResult.UnmatchedRight[_], _: MatchResult.UnmatchedLeft[_]) => 1 |
| 71 | + case (ur: MatchResult.UnmatchedRight[IterableEntry[T]], ur2: MatchResult.UnmatchedRight[IterableEntry[T]]) => |
| 72 | + iterableEntryOrdering.compare(ur.v, ur2.v) |
| 73 | + case (ur: MatchResult.UnmatchedLeft[IterableEntry[T]], m: MatchResult.Matched[IterableEntry[T]]) => |
| 74 | + iterableEntryOrdering.compare(ur.v, m.l) |
| 75 | + case (_: MatchResult.UnmatchedLeft[_], _: MatchResult.UnmatchedRight[_]) => -1 |
| 76 | + case (ur: MatchResult.UnmatchedLeft[IterableEntry[T]], ur2: MatchResult.UnmatchedLeft[IterableEntry[T]]) => |
| 77 | + iterableEntryOrdering.compare(ur.v, ur2.v) |
| 78 | + case (m1: MatchResult.Matched[IterableEntry[T]], m2: MatchResult.Matched[IterableEntry[T]]) => |
| 79 | + Ordering |
| 80 | + .by[MatchResult.Matched[IterableEntry[T]], (IterableEntry[T], IterableEntry[T])](m => (m.r, m.l)) |
| 81 | + .compare(m1, m2) |
| 82 | + case (m: MatchResult.Matched[IterableEntry[T]], ur: MatchResult.UnmatchedRight[IterableEntry[T]]) => |
| 83 | + iterableEntryOrdering.compare(m.r, ur.v) |
| 84 | + case (m: MatchResult.Matched[IterableEntry[T]], ul: MatchResult.UnmatchedLeft[IterableEntry[T]]) => |
| 85 | + iterableEntryOrdering.compare(m.l, ul.v) |
| 86 | + } |
| 87 | + } |
| 88 | + } |
| 89 | +} |
0 commit comments