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 (value) {
202 Float.POSITIVE_INFINITY -> writer.print("(1.0f/0.0f);")
203 Float.NEGATIVE_INFINITY -> writer.print("(-1.0f/0.0f);")
204 Float.NaN -> 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 (value) {
214 Double.POSITIVE_INFINITY -> writer.print("(1.0/0.0);")
215 Double.NEGATIVE_INFINITY -> writer.print("(-1.0/0.0);")
216 Double.NaN -> 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