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