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