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