1 /*
<lambda>null2 * Copyright (C) 2017 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.model
18
19 import java.io.PrintWriter
20
21 @MetalavaApi
22 interface FieldItem : MemberItem {
23 /** The property this field backs; inverse of [PropertyItem.backingField] */
24 val property: PropertyItem?
25 get() = null
26
27 /** The type of this field */
28 @MetalavaApi override fun type(): TypeItem
29
30 override fun findCorrespondingItemIn(
31 codebase: Codebase,
32 superMethods: Boolean,
33 duplicate: Boolean,
34 ) = containingClass().findCorrespondingItemIn(codebase)?.findField(name())
35
36 /**
37 * The initial/constant value, if any. If [requireConstant] the initial value will only be
38 * returned if it's constant.
39 */
40 fun initialValue(requireConstant: Boolean = true): Any?
41
42 /**
43 * An enum can contain both enum constants and fields; this method provides a way to distinguish
44 * between them.
45 */
46 fun isEnumConstant(): Boolean
47
48 /**
49 * Duplicates this field item.
50 *
51 * Override to specialize the return type.
52 */
53 override fun duplicate(targetContainingClass: ClassItem): FieldItem
54
55 override fun baselineElementId() = containingClass().qualifiedName() + "#" + name()
56
57 override fun accept(visitor: ItemVisitor) {
58 visitor.visit(this)
59 }
60
61 override fun toStringForItem() = "field ${containingClass().fullName()}.${name()}"
62
63 /**
64 * Check the declared value with a typed comparison, not a string comparison, to accommodate
65 * toolchains with different fp -> string conversions.
66 */
67 fun hasSameValue(other: FieldItem): Boolean {
68 val thisConstant = initialValue()
69 val otherConstant = other.initialValue()
70 if (thisConstant == null != (otherConstant == null)) {
71 return false
72 }
73
74 // Null values are considered equal
75 if (thisConstant == null) {
76 return true
77 }
78
79 if (type() != other.type()) {
80 return false
81 }
82
83 if (thisConstant == otherConstant) {
84 return true
85 }
86
87 if (thisConstant.toString() == otherConstant.toString()) {
88 // e.g. Integer(3) and Short(3) are the same; when comparing
89 // with signature files we sometimes don't have the right
90 // types from signatures
91 return true
92 }
93
94 return false
95 }
96
97 companion object {
98 val comparator: java.util.Comparator<FieldItem> = Comparator { a, b ->
99 a.name().compareTo(b.name())
100 }
101 }
102
103 /**
104 * If this field has an initial value, it just writes ";", otherwise it writes " = value;" with
105 * the correct Java syntax for the initial value
106 */
107 fun writeValueWithSemicolon(
108 writer: PrintWriter,
109 allowDefaultValue: Boolean = false,
110 requireInitialValue: Boolean = false
111 ) {
112 val value =
113 initialValue(!allowDefaultValue)
114 ?: if (allowDefaultValue && !containingClass().isClass()) type().defaultValue()
115 else null
116 if (value != null) {
117 when (value) {
118 is Int -> {
119 writer.print(" = ")
120 writer.print(value)
121 writer.print("; // 0x")
122 writer.print(Integer.toHexString(value))
123 }
124 is String -> {
125 writer.print(" = ")
126 writer.print('"')
127 writer.print(javaEscapeString(value))
128 writer.print('"')
129 writer.print(";")
130 }
131 is Long -> {
132 writer.print(" = ")
133 writer.print(value)
134 writer.print(String.format("L; // 0x%xL", value))
135 }
136 is Boolean -> {
137 writer.print(" = ")
138 writer.print(value)
139 writer.print(";")
140 }
141 is Byte -> {
142 writer.print(" = ")
143 writer.print(value)
144 writer.print("; // 0x")
145 writer.print(Integer.toHexString(value.toInt()))
146 }
147 is Short -> {
148 writer.print(" = ")
149 writer.print(value)
150 writer.print("; // 0x")
151 writer.print(Integer.toHexString(value.toInt()))
152 }
153 is Float -> {
154 writer.print(" = ")
155 when {
156 value == Float.POSITIVE_INFINITY -> writer.print("(1.0f/0.0f);")
157 value == Float.NEGATIVE_INFINITY -> writer.print("(-1.0f/0.0f);")
158 java.lang.Float.isNaN(value) -> writer.print("(0.0f/0.0f);")
159 // Force MIN_NORMAL to use the String representation created by
160 // java.lang.Float.toString() before the bug fix in JDK 19 - see
161 // https://inside.java/2022/09/23/quality-heads-up/ for details.
162 value == java.lang.Float.MIN_NORMAL ->
163 writer.format("1.17549435E-38f;", value)
164 else -> {
165 writer.print(canonicalizeFloatingPointString(value.toString()))
166 writer.print("f;")
167 }
168 }
169 }
170 is Double -> {
171 writer.print(" = ")
172 when {
173 value == Double.POSITIVE_INFINITY -> writer.print("(1.0/0.0);")
174 value == Double.NEGATIVE_INFINITY -> writer.print("(-1.0/0.0);")
175 java.lang.Double.isNaN(value) -> writer.print("(0.0/0.0);")
176 else -> {
177 writer.print(canonicalizeFloatingPointString(value.toString()))
178 writer.print(";")
179 }
180 }
181 }
182 is Char -> {
183 writer.print(" = ")
184 val intValue = value.code
185 writer.print(intValue)
186 writer.print("; // ")
187 writer.print(
188 String.format("0x%04x '%s'", intValue, javaEscapeString(value.toString()))
189 )
190 }
191 else -> {
192 writer.print(';')
193 }
194 }
195 } else {
196 // in interfaces etc we must have an initial value
197 if (requireInitialValue && !containingClass().isClass()) {
198 writer.print(" = null")
199 }
200 writer.print(';')
201 }
202 }
203 }
204
javaEscapeStringnull205 fun javaEscapeString(str: String): String {
206 var result = ""
207 val n = str.length
208 for (i in 0 until n) {
209 val c = str[i]
210 result +=
211 when (c) {
212 '\\' -> "\\\\"
213 '\t' -> "\\t"
214 '\b' -> "\\b"
215 '\r' -> "\\r"
216 '\n' -> "\\n"
217 '\'' -> "\\'"
218 '\"' -> "\\\""
219 in ' '..'~' -> c
220 else -> String.format("\\u%04x", c.code)
221 }
222 }
223 return result
224 }
225
226 // From doclava1 TextFieldItem#javaUnescapeString
227 @Suppress("LocalVariableName")
javaUnescapeStringnull228 fun javaUnescapeString(str: String): String {
229 val n = str.length
230 var simple = true
231 for (i in 0 until n) {
232 val c = str[i]
233 if (c == '\\') {
234 simple = false
235 break
236 }
237 }
238 if (simple) {
239 return str
240 }
241
242 val buf = StringBuilder(str.length)
243 var escaped: Char = 0.toChar()
244 val START = 0
245 val CHAR1 = 1
246 val CHAR2 = 2
247 val CHAR3 = 3
248 val CHAR4 = 4
249 val ESCAPE = 5
250 var state = START
251
252 for (i in 0 until n) {
253 val c = str[i]
254 when (state) {
255 START ->
256 if (c == '\\') {
257 state = ESCAPE
258 } else {
259 buf.append(c)
260 }
261 ESCAPE ->
262 when (c) {
263 '\\' -> {
264 buf.append('\\')
265 state = START
266 }
267 't' -> {
268 buf.append('\t')
269 state = START
270 }
271 'b' -> {
272 buf.append('\b')
273 state = START
274 }
275 'r' -> {
276 buf.append('\r')
277 state = START
278 }
279 'n' -> {
280 buf.append('\n')
281 state = START
282 }
283 '\'' -> {
284 buf.append('\'')
285 state = START
286 }
287 '\"' -> {
288 buf.append('\"')
289 state = START
290 }
291 'u' -> {
292 state = CHAR1
293 escaped = 0.toChar()
294 }
295 }
296 CHAR1,
297 CHAR2,
298 CHAR3,
299 CHAR4 -> {
300 escaped = (escaped.code shl 4).toChar()
301 escaped =
302 when (c) {
303 in '0'..'9' -> (escaped.code or (c - '0')).toChar()
304 in 'a'..'f' -> (escaped.code or (10 + (c - 'a'))).toChar()
305 in 'A'..'F' -> (escaped.code or (10 + (c - 'A'))).toChar()
306 else ->
307 throw IllegalArgumentException(
308 "bad escape sequence: '" +
309 c +
310 "' at pos " +
311 i +
312 " in: \"" +
313 str +
314 "\""
315 )
316 }
317 if (state == CHAR4) {
318 buf.append(escaped)
319 state = START
320 } else {
321 state++
322 }
323 }
324 }
325 }
326 if (state != START) {
327 throw IllegalArgumentException("unfinished escape sequence: $str")
328 }
329 return buf.toString()
330 }
331
332 /**
333 * Returns a canonical string representation of a floating point number. The representation is
334 * suitable for use as Java source code. This method also addresses bug #4428022 in the Sun JDK.
335 */
336 // From doclava1
canonicalizeFloatingPointStringnull337 fun canonicalizeFloatingPointString(value: String): String {
338 var str = value
339 if (str.indexOf('E') != -1) {
340 return str
341 }
342
343 // 1.0 is the only case where a trailing "0" is allowed.
344 // 1.00 is canonicalized as 1.0.
345 var i = str.length - 1
346 val d = str.indexOf('.')
347 while (i >= d + 2 && str[i] == '0') {
348 str = str.substring(0, i--)
349 }
350 return str
351 }
352