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