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