• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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