1 /*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.tools.metalava
18
19 import com.android.ide.common.process.CachedProcessOutputHandler
20 import com.android.ide.common.process.DefaultProcessExecutor
21 import com.android.ide.common.process.ProcessInfoBuilder
22 import com.android.utils.LineCollector
23 import com.android.utils.StdLogger
24 import java.io.File
25
26 // Copied from lint's test suite: TestUtils.diff in tools/base with
27 // some changes to allow running native diff.
28
29 /** Returns the universal diff for the two given files, or null if not supported or working */
getNativeDiffnull30 fun getNativeDiff(before: File, after: File): String? {
31 if (options.noNativeDiff) {
32 return null
33 }
34
35 // A lot faster for big files:
36 val native = File("/usr/bin/diff")
37 if (native.isFile) {
38 try {
39 val builder = ProcessInfoBuilder()
40 builder.setExecutable(native)
41 builder.addArgs("-u", before.path, after.path)
42 val processOutputHandler = CachedProcessOutputHandler()
43 DefaultProcessExecutor(StdLogger(StdLogger.Level.ERROR))
44 .execute(builder.createProcess(), processOutputHandler)
45 .rethrowFailure()
46
47 val output = processOutputHandler.processOutput
48 val lineCollector = LineCollector()
49 output.processStandardOutputLines(lineCollector)
50
51 return lineCollector.result.joinToString(separator = "\n")
52 } catch (ignore: Throwable) {
53 }
54 }
55
56 return null
57 }
58
getDiffnull59 fun getDiff(before: String, after: String, windowSize: Int): String {
60 return getDiff(
61 before.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray(),
62 after.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray(),
63 windowSize
64 )
65 }
66
getDiffnull67 fun getDiff(
68 before: Array<String>,
69 after: Array<String>,
70 windowSize: Int
71 ): String {
72 // Based on the LCS section in http://introcs.cs.princeton.edu/java/96optimization/
73 val sb = StringBuilder()
74 val n = before.size
75 val m = after.size
76
77 // Compute longest common subsequence of x[i..m] and y[j..n] bottom up
78 val lcs = Array(n + 1) { IntArray(m + 1) }
79 for (i in n - 1 downTo 0) {
80 for (j in m - 1 downTo 0) {
81 if (before[i] == after[j]) {
82 lcs[i][j] = lcs[i + 1][j + 1] + 1
83 } else {
84 lcs[i][j] = Math.max(lcs[i + 1][j], lcs[i][j + 1])
85 }
86 }
87 }
88
89 var i = 0
90 var j = 0
91 while (i < n && j < m) {
92 if (before[i] == after[j]) {
93 i++
94 j++
95 } else {
96 sb.append("@@ -")
97 sb.append(Integer.toString(i + 1))
98 sb.append(" +")
99 sb.append(Integer.toString(j + 1))
100 sb.append('\n')
101
102 if (windowSize > 0) {
103 for (context in Math.max(0, i - windowSize) until i) {
104 sb.append(" ")
105 sb.append(before[context])
106 sb.append("\n")
107 }
108 }
109
110 while (i < n && j < m && before[i] != after[j]) {
111 if (lcs[i + 1][j] >= lcs[i][j + 1]) {
112 sb.append('-')
113 if (!before[i].trim { it <= ' ' }.isEmpty()) {
114 sb.append(' ')
115 }
116 sb.append(before[i])
117 sb.append('\n')
118 i++
119 } else {
120 sb.append('+')
121 if (!after[j].trim { it <= ' ' }.isEmpty()) {
122 sb.append(' ')
123 }
124 sb.append(after[j])
125 sb.append('\n')
126 j++
127 }
128 }
129
130 if (windowSize > 0) {
131 for (context in i until Math.min(n, i + windowSize)) {
132 sb.append(" ")
133 sb.append(before[context])
134 sb.append("\n")
135 }
136 }
137 }
138 }
139
140 if (i < n || j < m) {
141 assert(i == n || j == m)
142 sb.append("@@ -")
143 sb.append(Integer.toString(i + 1))
144 sb.append(" +")
145 sb.append(Integer.toString(j + 1))
146 sb.append('\n')
147 while (i < n) {
148 sb.append('-')
149 if (!before[i].trim { it <= ' ' }.isEmpty()) {
150 sb.append(' ')
151 }
152 sb.append(before[i])
153 sb.append('\n')
154 i++
155 }
156 while (j < m) {
157 sb.append('+')
158 if (!after[j].trim { it <= ' ' }.isEmpty()) {
159 sb.append(' ')
160 }
161 sb.append(after[j])
162 sb.append('\n')
163 j++
164 }
165 }
166
167 return sb.toString()
168 }
169