• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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