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