1 /*
2 * Copyright (C) 2023 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 package com.android.hoststubgen
17
18 import java.io.PrintWriter
19 import java.util.zip.CRC32
20 import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
21 import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream
22 import org.apache.commons.compress.archivers.zip.ZipFile
23
24 /**
25 * Whether to skip compression when adding processed entries back to a zip file.
26 */
27 private const val SKIP_COMPRESSION = false
28
29 /**
30 * Name of this executable. Set it in the main method.
31 */
32 var executableName = "[command name not set]"
33
34 /**
35 * A regex that maches whitespate.
36 */
37 val whitespaceRegex = """\s+""".toRegex()
38
39 /**
40 * Remove the comment ('#' and following) and surrounding whitespace from a line.
41 */
normalizeTextLinenull42 fun normalizeTextLine(s: String): String {
43 // Remove # and after. (comment)
44 val pos = s.indexOf('#')
45 val uncommented = if (pos < 0) s else s.substring(0, pos)
46
47 // Remove surrounding whitespace.
48 return uncommented.trim()
49 }
50
51 /**
52 * Concatenate list [a] and [b] and return it. As an optimization, it returns an input
53 * [List] as-is if the other [List] is empty, so do not modify input [List]'s.
54 */
addListsnull55 fun <T> addLists(a: List<T>, b: List<T>): List<T> {
56 if (a.isEmpty()) {
57 return b
58 }
59 if (b.isEmpty()) {
60 return a
61 }
62 return a + b
63 }
64
65 /**
66 * Add element [b] to list [a] if [b] is not null. Otherwise, just return [a].
67 * (because the method may return [a] as-is, do not modify it after passing it.)
68 */
addNonNullElementnull69 fun <T> addNonNullElement(a: List<T>, b: T?): List<T> {
70 if (b == null) {
71 return a
72 }
73 if (a.isEmpty()) {
74 return listOf(b)
75 }
76 return a + b
77 }
78
79
80 /**
81 * Exception for a parse error in a file
82 */
83 class ParseException : Exception, UserErrorException {
84 val hasSourceInfo: Boolean
85
86 constructor(message: String) : super(message) {
87 hasSourceInfo = false
88 }
89
90 constructor(message: String, file: String, line: Int) :
91 super("$message in file $file line $line") {
92 hasSourceInfo = true
93 }
94
withSourceInfonull95 fun withSourceInfo(filename: String, lineNo: Int): ParseException {
96 if (hasSourceInfo) {
97 return this // Already has source information.
98 } else {
99 return ParseException(this.message ?: "", filename, lineNo)
100 }
101 }
102 }
103
104 /**
105 * Escape a string for a CSV field.
106 */
csvEscapenull107 fun csvEscape(value: String): String {
108 return "\"" + value.replace("\"", "\"\"") + "\""
109 }
110
runMainWithBoilerplatenull111 inline fun runMainWithBoilerplate(realMain: () -> Unit) {
112 var success = false
113
114 try {
115 realMain()
116
117 success = true
118 } catch (e: Throwable) {
119 log.e("$executableName: Error: ${e.message}")
120 if (e !is UserErrorException) {
121 e.printStackTrace(PrintWriter(log.getWriter(LogLevel.Error)))
122 }
123 } finally {
124 log.i("$executableName finished")
125 log.flush()
126 }
127
128 System.exit(if (success) 0 else 1 )
129 }
130
131 /**
132 * Copy a single ZIP entry to the output.
133 */
copyZipEntrynull134 fun copyZipEntry(
135 inZip: ZipFile,
136 entry: ZipArchiveEntry,
137 out: ZipArchiveOutputStream,
138 ) {
139 inZip.getRawInputStream(entry).use { out.addRawArchiveEntry(entry, it) }
140 }
141
142 /**
143 * Add a single ZIP entry with data.
144 */
ZipArchiveOutputStreamnull145 fun ZipArchiveOutputStream.addBytesEntry(name: String, data: ByteArray) {
146 val newEntry = ZipArchiveEntry(name)
147 if (SKIP_COMPRESSION) {
148 newEntry.method = 0
149 newEntry.size = data.size.toLong()
150 newEntry.crc = CRC32().apply { update(data) }.value
151 }
152 putArchiveEntry(newEntry)
153 write(data)
154 closeArchiveEntry()
155 }
156