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