• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 Google, 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 @file:JvmName("ClassNames")
17 
18 package com.squareup.kotlinpoet
19 
20 import javax.lang.model.element.Element
21 import javax.lang.model.element.ElementKind
22 import javax.lang.model.element.NestingKind.MEMBER
23 import javax.lang.model.element.NestingKind.TOP_LEVEL
24 import javax.lang.model.element.PackageElement
25 import javax.lang.model.element.TypeElement
26 import kotlin.reflect.KClass
27 
28 /** A fully-qualified class name for top-level and member classes.  */
29 public class ClassName internal constructor(
30   names: List<String>,
31   nullable: Boolean = false,
32   annotations: List<AnnotationSpec> = emptyList(),
33   tags: Map<KClass<*>, Any> = emptyMap()
34 ) : TypeName(nullable, annotations, TagMap(tags)), Comparable<ClassName> {
35   /**
36    * Returns a class name created from the given parts. For example, calling this with package name
37    * `"java.util"` and simple names `"Map"`, `"Entry"` yields `Map.Entry`.
38    */
39   @Deprecated("", level = DeprecationLevel.HIDDEN)
40   public constructor(packageName: String, simpleName: String, vararg simpleNames: String) :
41     this(listOf(packageName, simpleName, *simpleNames))
42 
43   @Deprecated(
44     "Simple names must not be empty. Did you forget an argument?",
45     level = DeprecationLevel.ERROR,
46     replaceWith = ReplaceWith("ClassName(packageName, TODO())"),
47   )
48   public constructor(packageName: String) : this(packageName, listOf())
49 
50   /**
51    * Returns a class name created from the given parts. For example, calling this with package name
52    * `"java.util"` and simple names `"Map"`, `"Entry"` yields `Map.Entry`.
53    */
54   public constructor(packageName: String, vararg simpleNames: String) :
55     this(listOf(packageName, *simpleNames)) {
<lambda>null56       require(simpleNames.isNotEmpty()) { "simpleNames must not be empty" }
<lambda>null57       require(simpleNames.none { it.isEmpty() }) {
58         "simpleNames must not contain empty items: ${simpleNames.contentToString()}"
59       }
60     }
61 
62   /**
63    * Returns a class name created from the given parts. For example, calling this with package name
64    * `"java.util"` and simple names `"Map"`, `"Entry"` yields `Map.Entry`.
65    */
66   public constructor(packageName: String, simpleNames: List<String>) :
<lambda>null67     this(mutableListOf(packageName).apply { addAll(simpleNames) }) {
<lambda>null68       require(simpleNames.isNotEmpty()) { "simpleNames must not be empty" }
<lambda>null69       require(simpleNames.none { it.isEmpty() }) {
70         "simpleNames must not contain empty items: $simpleNames"
71       }
72     }
73 
74   /** From top to bottom. This will be `["java.util", "Map", "Entry"]` for `Map.Entry`. */
75   private val names = names.toImmutableList()
76 
77   /** Fully qualified name using `.` as a separator, like `kotlin.collections.Map.Entry`. */
78   public val canonicalName: String = if (names[0].isEmpty())
79     names.subList(1, names.size).joinToString(".") else
80     names.joinToString(".")
81 
82   /** Package name, like `"kotlin.collections"` for `Map.Entry`. */
83   public val packageName: String get() = names[0]
84 
85   /** Simple name of this class, like `"Entry"` for `Map.Entry`. */
86   public val simpleName: String get() = names[names.size - 1]
87 
88   /**
89    * The enclosing classes, outermost first, followed by the simple name. This is `["Map", "Entry"]`
90    * for `Map.Entry`.
91    */
92   public val simpleNames: List<String> get() = names.subList(1, names.size)
93 
copynull94   override fun copy(
95     nullable: Boolean,
96     annotations: List<AnnotationSpec>,
97     tags: Map<KClass<*>, Any>
98   ): ClassName {
99     return ClassName(names, nullable, annotations, tags)
100   }
101 
102   /**
103    * Returns the enclosing class, like `Map` for `Map.Entry`. Returns null if this class is not
104    * nested in another class.
105    */
enclosingClassNamenull106   public fun enclosingClassName(): ClassName? {
107     return if (names.size != 2)
108       ClassName(names.subList(0, names.size - 1)) else
109       null
110   }
111 
112   /**
113    * Returns the top class in this nesting group. Equivalent to chained calls to
114    * [ClassName.enclosingClassName] until the result's enclosing class is null.
115    */
topLevelClassNamenull116   public fun topLevelClassName(): ClassName = ClassName(names.subList(0, 2))
117 
118   /**
119    * Fully qualified name using `.` to separate package from the top level class name, and `$` to
120    * separate nested classes, like `kotlin.collections.Map$Entry`.
121    */
122   public fun reflectionName(): String {
123     // trivial case: no nested names
124     if (names.size == 2) {
125       return if (packageName.isEmpty())
126         names[1] else
127         packageName + "." + names[1]
128     }
129     // concat top level class name and nested names
130     return buildString {
131       append(topLevelClassName().canonicalName)
132       for (name in simpleNames.subList(1, simpleNames.size)) {
133         append('$').append(name)
134       }
135     }
136   }
137 
138   /**
139    * Callable reference to the constructor of this class. Emits the enclosing class if one exists,
140    * followed by the reference operator `::`, followed by either [simpleName] or the
141    * fully-qualified name if this is a top-level class.
142    *
143    * Note: As `::$packageName.$simpleName` is not valid syntax, an aliased import may be required
144    * for a top-level class with a conflicting name.
145    */
constructorReferencenull146   public fun constructorReference(): CodeBlock {
147     val enclosing = enclosingClassName()
148     return if (enclosing != null) {
149       CodeBlock.of("%T::%N", enclosing, simpleName)
150     } else {
151       CodeBlock.of("::%T", this)
152     }
153   }
154 
155   /** Returns a new [ClassName] instance for the specified `name` as nested inside this class. */
nestedClassnull156   public fun nestedClass(name: String): ClassName = ClassName(names + name)
157 
158   /**
159    * Returns a class that shares the same enclosing package or class. If this class is enclosed by
160    * another class, this is equivalent to `enclosingClassName().nestedClass(name)`. Otherwise
161    * it is equivalent to `get(packageName(), name)`.
162    */
163   public fun peerClass(name: String): ClassName {
164     val result = names.toMutableList()
165     result[result.size - 1] = name
166     return ClassName(result)
167   }
168 
169   /**
170    * Orders by the fully-qualified name. Nested types are ordered immediately after their
171    * enclosing type. For example, the following types are ordered by this method:
172    *
173    * ```
174    * com.example.Robot
175    * com.example.Robot.Motor
176    * com.example.RoboticVacuum
177    * ```
178    */
compareTonull179   override fun compareTo(other: ClassName): Int = canonicalName.compareTo(other.canonicalName)
180 
181   override fun emit(out: CodeWriter) =
182     out.emit(out.lookupName(this).escapeSegmentsIfNecessary())
183 
184   public companion object {
185     /**
186      * Returns a new [ClassName] instance for the given fully-qualified class name string. This
187      * method assumes that the input is ASCII and follows typical Java style (lowercase package
188      * names, UpperCamelCase class names) and may produce incorrect results or throw
189      * [IllegalArgumentException] otherwise. For that reason, the constructor should be preferred as
190      * it can create [ClassName] instances without such restrictions.
191      */
192     @JvmStatic public fun bestGuess(classNameString: String): ClassName {
193       val names = mutableListOf<String>()
194 
195       // Add the package name, like "java.util.concurrent", or "" for no package.
196       var p = 0
197       while (p < classNameString.length && Character.isLowerCase(classNameString.codePointAt(p))) {
198         p = classNameString.indexOf('.', p) + 1
199         require(p != 0) { "couldn't make a guess for $classNameString" }
200       }
201       names += if (p != 0) classNameString.substring(0, p - 1) else ""
202 
203       // Add the class names, like "Map" and "Entry".
204       for (part in classNameString.substring(p).split('.')) {
205         require(part.isNotEmpty() && Character.isUpperCase(part.codePointAt(0))) {
206           "couldn't make a guess for $classNameString"
207         }
208 
209         names += part
210       }
211 
212       require(names.size >= 2) { "couldn't make a guess for $classNameString" }
213       return ClassName(names)
214     }
215   }
216 }
217 
218 @DelicateKotlinPoetApi(
219   message = "Java reflection APIs don't give complete information on Kotlin types. Consider using" +
220     " the kotlinpoet-metadata APIs instead."
221 )
222 @JvmName("get")
asClassNamenull223 public fun Class<*>.asClassName(): ClassName {
224   require(!isPrimitive) { "primitive types cannot be represented as a ClassName" }
225   require(Void.TYPE != this) { "'void' type cannot be represented as a ClassName" }
226   require(!isArray) { "array types cannot be represented as a ClassName" }
227   val names = mutableListOf<String>()
228   var c = this
229   while (true) {
230     names += c.simpleName
231     val enclosing = c.enclosingClass ?: break
232     c = enclosing
233   }
234   // Avoid unreliable Class.getPackage(). https://github.com/square/javapoet/issues/295
235   val lastDot = c.name.lastIndexOf('.')
236   if (lastDot != -1) names += c.name.substring(0, lastDot)
237   names.reverse()
238   return ClassName(names)
239 }
240 
241 @JvmName("get")
asClassNamenull242 public fun KClass<*>.asClassName(): ClassName {
243   qualifiedName?.let { return ClassName.bestGuess(it) }
244   throw IllegalArgumentException("$this cannot be represented as a ClassName")
245 }
246 
247 /** Returns the class name for `element`. */
248 @DelicateKotlinPoetApi(
249   message = "Element APIs don't give complete information on Kotlin types. Consider using" +
250     " the kotlinpoet-metadata APIs instead."
251 )
252 @JvmName("get")
asClassNamenull253 public fun TypeElement.asClassName(): ClassName {
254   fun isClassOrInterface(e: Element) = e.kind.isClass || e.kind.isInterface
255 
256   fun getPackage(type: Element): PackageElement {
257     var t = type
258     while (t.kind != ElementKind.PACKAGE) {
259       t = t.enclosingElement
260     }
261     return t as PackageElement
262   }
263 
264   val names = mutableListOf<String>()
265   var e: Element = this
266   while (isClassOrInterface(e)) {
267     val eType = e as TypeElement
268     require(eType.nestingKind.isOneOf(TOP_LEVEL, MEMBER)) {
269       "unexpected type testing"
270     }
271     names += eType.simpleName.toString()
272     e = eType.enclosingElement
273   }
274   names += getPackage(this).qualifiedName.toString()
275   names.reverse()
276   return ClassName(names)
277 }
278