Home | History | Annotate | Download | only in model
      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