1 /* <lambda>null2 * Copyright (C) 2021 Square, Inc. 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 * https://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 package com.squareup.kotlinpoet.metadata.classinspectors 17 18 import com.squareup.kotlinpoet.AnnotationSpec 19 import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget 20 import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FIELD 21 import com.squareup.kotlinpoet.CHAR_SEQUENCE 22 import com.squareup.kotlinpoet.COLLECTION 23 import com.squareup.kotlinpoet.COMPARABLE 24 import com.squareup.kotlinpoet.ClassName 25 import com.squareup.kotlinpoet.CodeBlock 26 import com.squareup.kotlinpoet.ITERABLE 27 import com.squareup.kotlinpoet.LIST 28 import com.squareup.kotlinpoet.MAP 29 import com.squareup.kotlinpoet.MAP_ENTRY 30 import com.squareup.kotlinpoet.MUTABLE_COLLECTION 31 import com.squareup.kotlinpoet.MUTABLE_ITERABLE 32 import com.squareup.kotlinpoet.MUTABLE_LIST 33 import com.squareup.kotlinpoet.MUTABLE_MAP 34 import com.squareup.kotlinpoet.MUTABLE_MAP_ENTRY 35 import com.squareup.kotlinpoet.MUTABLE_SET 36 import com.squareup.kotlinpoet.SET 37 import com.squareup.kotlinpoet.TypeName 38 import com.squareup.kotlinpoet.asClassName 39 import com.squareup.kotlinpoet.joinToCode 40 import com.squareup.kotlinpoet.metadata.specs.ClassInspector 41 import java.util.Collections 42 import java.util.TreeSet 43 import kotlin.metadata.KmProperty 44 import kotlin.metadata.isConst 45 import kotlin.metadata.isLocalClassName 46 import org.jetbrains.annotations.NotNull 47 import org.jetbrains.annotations.Nullable 48 49 internal object ClassInspectorUtil { 50 val JVM_NAME: ClassName = JvmName::class.asClassName() 51 private val JVM_FIELD = JvmField::class.asClassName() 52 internal val JVM_FIELD_SPEC = AnnotationSpec.builder(JVM_FIELD).build() 53 internal val JVM_SYNTHETIC = JvmSynthetic::class.asClassName() 54 internal val JVM_SYNTHETIC_SPEC = AnnotationSpec.builder(JVM_SYNTHETIC).build() 55 internal val JAVA_DEPRECATED = java.lang.Deprecated::class.asClassName() 56 private val JVM_TRANSIENT = Transient::class.asClassName() 57 private val JVM_VOLATILE = Volatile::class.asClassName() 58 private val IMPLICIT_FIELD_ANNOTATIONS = setOf( 59 JVM_FIELD, 60 JVM_TRANSIENT, 61 JVM_VOLATILE, 62 ) 63 private val NOT_NULL = NotNull::class.asClassName() 64 private val NULLABLE = Nullable::class.asClassName() 65 private val EXTENSION_FUNCTION_TYPE = ExtensionFunctionType::class.asClassName() 66 private val KOTLIN_INTRINSIC_ANNOTATIONS = setOf( 67 NOT_NULL, 68 NULLABLE, 69 EXTENSION_FUNCTION_TYPE, 70 ) 71 72 val KOTLIN_INTRINSIC_INTERFACES: Set<ClassName> = setOf( 73 CHAR_SEQUENCE, 74 COMPARABLE, 75 ITERABLE, 76 COLLECTION, 77 LIST, 78 SET, 79 MAP, 80 MAP_ENTRY, 81 MUTABLE_ITERABLE, 82 MUTABLE_COLLECTION, 83 MUTABLE_LIST, 84 MUTABLE_SET, 85 MUTABLE_MAP, 86 MUTABLE_MAP_ENTRY, 87 ) 88 89 private val KOTLIN_NULLABILITY_ANNOTATIONS = setOf( 90 "org.jetbrains.annotations.NotNull", 91 "org.jetbrains.annotations.Nullable", 92 ) 93 94 fun filterOutNullabilityAnnotations( 95 annotations: List<AnnotationSpec>, 96 ): List<AnnotationSpec> { 97 return annotations.filterNot { 98 val typeName = it.typeName 99 return@filterNot typeName is ClassName && 100 typeName.canonicalName in KOTLIN_NULLABILITY_ANNOTATIONS 101 } 102 } 103 104 /** @return a [CodeBlock] representation of a [literal] value. */ 105 fun codeLiteralOf(literal: Any): CodeBlock { 106 return when (literal) { 107 is String -> CodeBlock.of("%S", literal) 108 is Long -> CodeBlock.of("%LL", literal) 109 is Float -> CodeBlock.of("%LF", literal) 110 else -> CodeBlock.of("%L", literal) 111 } 112 } 113 114 /** 115 * Infers if [property] is a jvm field and should be annotated as such given the input 116 * parameters. 117 */ 118 fun computeIsJvmField( 119 property: KmProperty, 120 classInspector: ClassInspector, 121 isCompanionObject: Boolean, 122 hasGetter: Boolean, 123 hasSetter: Boolean, 124 hasField: Boolean, 125 ): Boolean { 126 return if (!hasGetter && 127 !hasSetter && 128 hasField && 129 !property.isConst 130 ) { 131 !(classInspector.supportsNonRuntimeRetainedAnnotations && !isCompanionObject) 132 } else { 133 false 134 } 135 } 136 137 /** 138 * @return a new collection of [AnnotationSpecs][AnnotationSpec] with sorting and de-duping 139 * input annotations from [body]. 140 */ 141 fun createAnnotations( 142 siteTarget: UseSiteTarget? = null, 143 body: MutableCollection<AnnotationSpec>.() -> Unit, 144 ): Collection<AnnotationSpec> { 145 val result = mutableSetOf<AnnotationSpec>() 146 .apply(body) 147 .filterNot { spec -> 148 spec.typeName in KOTLIN_INTRINSIC_ANNOTATIONS 149 } 150 val withUseSiteTarget = if (siteTarget != null) { 151 result.map { 152 if (!(siteTarget == FIELD && it.typeName in IMPLICIT_FIELD_ANNOTATIONS)) { 153 // Some annotations are implicitly only for FIELD, so don't emit those site targets 154 it.toBuilder().useSiteTarget(siteTarget).build() 155 } else { 156 it 157 } 158 } 159 } else { 160 result 161 } 162 163 val sorted = withUseSiteTarget.toTreeSet() 164 165 return Collections.unmodifiableCollection(sorted) 166 } 167 168 /** 169 * @return a [@Throws][Throws] [AnnotationSpec] representation of a given collection of 170 * [exceptions]. 171 */ 172 fun createThrowsSpec( 173 exceptions: Collection<TypeName>, 174 useSiteTarget: UseSiteTarget? = null, 175 ): AnnotationSpec { 176 return AnnotationSpec.builder(Throws::class) 177 .addMember( 178 "exceptionClasses = %L", 179 exceptions.map { CodeBlock.of("%T::class", it) } 180 .joinToCode(prefix = "[", suffix = "]"), 181 ) 182 .useSiteTarget(useSiteTarget) 183 .build() 184 } 185 186 /** 187 * Best guesses a [ClassName] as represented in Metadata's [kotlin.metadata.ClassName], where 188 * package names in this name are separated by '/' and class names are separated by '.'. 189 * 190 * For example: `"org/foo/bar/Baz.Nested"`. 191 * 192 * Local classes are prefixed with ".", but for KotlinPoetMetadataSpecs' use case we don't deal 193 * with those. 194 */ 195 fun createClassName(kotlinMetadataName: String): ClassName { 196 require(!kotlinMetadataName.isLocalClassName()) { 197 "Local/anonymous classes are not supported!" 198 } 199 // Top-level: package/of/class/MyClass 200 // Nested A: package/of/class/MyClass.NestedClass 201 val simpleName = kotlinMetadataName.substringAfterLast( 202 '/', // Drop the package name, e.g. "package/of/class/" 203 '.', // Drop any enclosing classes, e.g. "MyClass." 204 ) 205 val packageName = kotlinMetadataName.substringBeforeLast( 206 delimiter = "/", 207 missingDelimiterValue = "", 208 ) 209 val simpleNames = kotlinMetadataName.removeSuffix(simpleName) 210 .removeSuffix(".") // Trailing "." if any 211 .removePrefix(packageName) 212 .removePrefix("/") 213 .let { 214 if (it.isNotEmpty()) { 215 it.split(".") 216 } else { 217 // Don't split, otherwise we end up with an empty string as the first element! 218 emptyList() 219 } 220 } 221 .plus(simpleName) 222 223 return ClassName( 224 packageName = packageName.replace("/", "."), 225 simpleNames = simpleNames, 226 ) 227 } 228 229 fun Iterable<AnnotationSpec>.toTreeSet(): TreeSet<AnnotationSpec> { 230 return TreeSet<AnnotationSpec>(compareBy { it.toString() }).apply { 231 addAll(this@toTreeSet) 232 } 233 } 234 235 private fun String.substringAfterLast(vararg delimiters: Char): String { 236 val index = lastIndexOfAny(delimiters) 237 return if (index == -1) this else substring(index + 1, length) 238 } 239 } 240