blob: e65bda9ed2b914668fc1e60f8c1409c5b939982e [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.metalava.model
import com.android.tools.metalava.model.visitors.ItemVisitor
import com.android.tools.metalava.model.visitors.TypeVisitor
import com.intellij.psi.PsiField
import java.io.PrintWriter
interface FieldItem : MemberItem {
/** The type of this field */
override fun type(): TypeItem
/**
* The initial/constant value, if any. If [requireConstant] the initial value will
* only be returned if it's constant.
*/
fun initialValue(requireConstant: Boolean = true): Any?
/**
* An enum can contain both enum constants and fields; this method provides a way
* to distinguish between them.
*/
fun isEnumConstant(): Boolean
/**
* If this field is inherited from a hidden super class, this property is set.
* This is necessary because these fields should not be listed in signature files,
* whereas in stub files it's necessary for them to be included.
*/
var inheritedField: Boolean
/**
* If this field is copied from a super class (typically via [duplicate]) this
* field points to the original class it was copied from
*/
var inheritedFrom: ClassItem?
/**
* Duplicates this field item. Used when we need to insert inherited fields from
* interfaces etc.
*/
fun duplicate(targetContainingClass: ClassItem): FieldItem
override fun accept(visitor: ItemVisitor) {
if (visitor.skip(this)) {
return
}
visitor.visitItem(this)
visitor.visitField(this)
visitor.afterVisitField(this)
visitor.afterVisitItem(this)
}
override fun acceptTypes(visitor: TypeVisitor) {
if (visitor.skip(this)) {
return
}
val type = type()
visitor.visitType(type, this)
visitor.afterVisitType(type, this)
}
/**
* Check the declared value with a typed comparison, not a string comparison,
* to accommodate toolchains with different fp -> string conversions.
*/
fun hasSameValue(other: FieldItem): Boolean {
val thisConstant = initialValue(true)
val otherConstant = other.initialValue(true)
if (thisConstant == null != (otherConstant == null)) {
return false
}
// Null values are considered equal
if (thisConstant == null) {
return true
}
if (type() != other.type()) {
return false
}
if (thisConstant == otherConstant) {
return true
}
if (thisConstant.toString() == otherConstant.toString()) {
// e.g. Integer(3) and Short(3) are the same; when comparing
// with signature files we sometimes don't have the right
// types from signatures
return true
}
// Try a little harder when we're dealing with PsiElements
if (thisConstant is PsiField && otherConstant is PsiField) {
val name1 = thisConstant.name
val name2 = otherConstant.name
if (name1 == name2) {
val qualifiedName1 = thisConstant.containingClass?.qualifiedName
val qualifiedName2 = otherConstant.containingClass?.qualifiedName
return qualifiedName1 == qualifiedName2
}
}
return false
}
override fun hasNullnessInfo(): Boolean {
if (!requiresNullnessInfo()) {
return true
}
return modifiers.hasNullnessInfo()
}
override fun requiresNullnessInfo(): Boolean {
if (type().primitive) {
return false
}
if (modifiers.isFinal() && initialValue(true) != null) {
return false
}
return true
}
companion object {
val comparator: java.util.Comparator<FieldItem> = Comparator { a, b -> a.name().compareTo(b.name()) }
}
/**
* If this field has an initial value, it just writes ";", otherwise it writes
* " = value;" with the correct Java syntax for the initial value
*/
fun writeValueWithSemicolon(
writer: PrintWriter,
allowDefaultValue: Boolean = false,
requireInitialValue: Boolean = false
) {
val value =
initialValue(!allowDefaultValue)
?: if (allowDefaultValue && !containingClass().isClass()) type().defaultValue() else null
if (value != null) {
when (value) {
is Int -> {
writer.print(" = ")
writer.print(value)
writer.print("; // 0x")
writer.print(Integer.toHexString(value))
}
is String -> {
writer.print(" = ")
writer.print('"')
writer.print(javaEscapeString(value))
writer.print('"')
writer.print(";")
}
is Long -> {
writer.print(" = ")
writer.print(value)
writer.print(String.format("L; // 0x%xL", value))
}
is Boolean -> {
writer.print(" = ")
writer.print(value)
writer.print(";")
}
is Byte -> {
writer.print(" = ")
writer.print(value)
writer.print("; // 0x")
writer.print(Integer.toHexString(value.toInt()))
}
is Short -> {
writer.print(" = ")
writer.print(value)
writer.print("; // 0x")
writer.print(Integer.toHexString(value.toInt()))
}
is Float -> {
writer.print(" = ")
when (value) {
Float.POSITIVE_INFINITY -> writer.print("(1.0f/0.0f);")
Float.NEGATIVE_INFINITY -> writer.print("(-1.0f/0.0f);")
Float.NaN -> writer.print("(0.0f/0.0f);")
else -> {
writer.print(canonicalizeFloatingPointString(value.toString()))
writer.print("f;")
}
}
}
is Double -> {
writer.print(" = ")
when (value) {
Double.POSITIVE_INFINITY -> writer.print("(1.0/0.0);")
Double.NEGATIVE_INFINITY -> writer.print("(-1.0/0.0);")
Double.NaN -> writer.print("(0.0/0.0);")
else -> {
writer.print(canonicalizeFloatingPointString(value.toString()))
writer.print(";")
}
}
}
is Char -> {
writer.print(" = ")
val intValue = value.toInt()
writer.print(intValue)
writer.print("; // ")
writer.print(
String.format(
"0x%04x '%s'", intValue,
javaEscapeString(value.toString())
)
)
}
else -> {
writer.print(';')
}
}
} else {
// in interfaces etc we must have an initial value
if (requireInitialValue && !containingClass().isClass()) {
writer.print(" = null")
}
writer.print(';')
}
}
}
fun javaEscapeString(str: String): String {
var result = ""
val n = str.length
for (i in 0 until n) {
val c = str[i]
result += when (c) {
'\\' -> "\\\\"
'\t' -> "\\t"
'\b' -> "\\b"
'\r' -> "\\r"
'\n' -> "\\n"
'\'' -> "\\'"
'\"' -> "\\\""
in ' '..'~' -> c
else -> String.format("\\u%04x", c.toInt())
}
}
return result
}
@Suppress("LocalVariableName")
// From doclava1 TextFieldItem#javaUnescapeString
fun javaUnescapeString(str: String): String {
val n = str.length
var simple = true
for (i in 0 until n) {
val c = str[i]
if (c == '\\') {
simple = false
break
}
}
if (simple) {
return str
}
val buf = StringBuilder(str.length)
var escaped: Char = 0.toChar()
val START = 0
val CHAR1 = 1
val CHAR2 = 2
val CHAR3 = 3
val CHAR4 = 4
val ESCAPE = 5
var state = START
for (i in 0 until n) {
val c = str[i]
when (state) {
START -> if (c == '\\') {
state = ESCAPE
} else {
buf.append(c)
}
ESCAPE -> when (c) {
'\\' -> {
buf.append('\\')
state = START
}
't' -> {
buf.append('\t')
state = START
}
'b' -> {
buf.append('\b')
state = START
}
'r' -> {
buf.append('\r')
state = START
}
'n' -> {
buf.append('\n')
state = START
}
'\'' -> {
buf.append('\'')
state = START
}
'\"' -> {
buf.append('\"')
state = START
}
'u' -> {
state = CHAR1
escaped = 0.toChar()
}
}
CHAR1, CHAR2, CHAR3, CHAR4 -> {
escaped = (escaped.toInt() shl 4).toChar()
escaped = when (c) {
in '0'..'9' -> (escaped.toInt() or (c - '0')).toChar()
in 'a'..'f' -> (escaped.toInt() or (10 + (c - 'a'))).toChar()
in 'A'..'F' -> (escaped.toInt() or (10 + (c - 'A'))).toChar()
else -> throw IllegalArgumentException(
"bad escape sequence: '" + c + "' at pos " + i + " in: \"" +
str + "\""
)
}
if (state == CHAR4) {
buf.append(escaped)
state = START
} else {
state++
}
}
}
}
if (state != START) {
throw IllegalArgumentException("unfinished escape sequence: $str")
}
return buf.toString()
}
/**
* Returns a canonical string representation of a floating point
* number. The representation is suitable for use as Java source
* code. This method also addresses bug #4428022 in the Sun JDK.
*/
// From doclava1
fun canonicalizeFloatingPointString(value: String): String {
var str = value
if (str.indexOf('E') != -1) {
return str
}
// 1.0 is the only case where a trailing "0" is allowed.
// 1.00 is canonicalized as 1.0.
var i = str.length - 1
val d = str.indexOf('.')
while (i >= d + 2 && str[i] == '0') {
str = str.substring(0, i--)
}
return str
}