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