1 /*
<lambda>null2 * Copyright (C) 2015 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
17
18 import java.io.Closeable
19 import kotlin.math.min
20
21 /** Sentinel value that indicates that no user-provided package has been set. */
22 private val NO_PACKAGE = String()
23
24 internal val NULLABLE_ANY = ANY.copy(nullable = true)
25
26 private fun extractMemberName(part: String): String {
27 require(Character.isJavaIdentifierStart(part[0])) { "not an identifier: $part" }
28 for (i in 1..part.length) {
29 if (!part.substring(0, i).isIdentifier) {
30 return part.substring(0, i - 1)
31 }
32 }
33 return part
34 }
35
buildCodeStringnull36 internal inline fun buildCodeString(builderAction: CodeWriter.() -> Unit): String {
37 val stringBuilder = StringBuilder()
38 CodeWriter(stringBuilder, columnLimit = Integer.MAX_VALUE).use {
39 it.builderAction()
40 }
41 return stringBuilder.toString()
42 }
43
buildCodeStringnull44 internal fun buildCodeString(
45 codeWriter: CodeWriter,
46 builderAction: CodeWriter.() -> Unit,
47 ): String {
48 val stringBuilder = StringBuilder()
49 codeWriter.emitInto(stringBuilder, builderAction)
50 return stringBuilder.toString()
51 }
52
53 /**
54 * Converts a [FileSpec] to a string suitable to both human- and kotlinc-consumption. This honors
55 * imports, indentation, and deferred variable names.
56 */
57 internal class CodeWriter constructor(
58 out: Appendable,
59 private val indent: String = DEFAULT_INDENT,
60 imports: Map<String, Import> = emptyMap(),
61 private val importedTypes: Map<String, ClassName> = emptyMap(),
62 private val importedMembers: Map<String, MemberName> = emptyMap(),
63 columnLimit: Int = 100,
64 ) : Closeable {
65 private var out = LineWrapper(out, indent, columnLimit)
66 private var indentLevel = 0
67
68 private var kdoc = false
69 private var comment = false
70 private var packageName = NO_PACKAGE
71 private val typeSpecStack = mutableListOf<TypeSpec>()
72 private val memberImportNames = mutableSetOf<String>()
<lambda>null73 private val importableTypes = mutableMapOf<String, List<ClassName>>().withDefault { emptyList() }
<lambda>null74 private val importableMembers = mutableMapOf<String, List<MemberName>>().withDefault { emptyList() }
75 private val referencedNames = mutableSetOf<String>()
76 private var trailingNewline = false
77
<lambda>null78 val imports = imports.also {
79 for ((memberName, _) in imports) {
80 val lastDotIndex = memberName.lastIndexOf('.')
81 if (lastDotIndex >= 0) {
82 memberImportNames.add(memberName.substring(0, lastDotIndex))
83 }
84 }
85 }
86
87 /**
88 * When emitting a statement, this is the line of the statement currently being written. The first
89 * line of a statement is indented normally and subsequent wrapped lines are double-indented. This
90 * is -1 when the currently-written line isn't part of a statement.
91 */
92 var statementLine = -1
93
<lambda>null94 fun indent(levels: Int = 1) = apply {
95 indentLevel += levels
96 }
97
<lambda>null98 fun unindent(levels: Int = 1) = apply {
99 require(indentLevel - levels >= 0) { "cannot unindent $levels from $indentLevel" }
100 indentLevel -= levels
101 }
102
<lambda>null103 fun pushPackage(packageName: String) = apply {
104 check(this.packageName === NO_PACKAGE) { "package already set: ${this.packageName}" }
105 this.packageName = packageName
106 }
107
<lambda>null108 fun popPackage() = apply {
109 check(packageName !== NO_PACKAGE) { "package already set: $packageName" }
110 packageName = NO_PACKAGE
111 }
112
<lambda>null113 fun pushType(type: TypeSpec) = apply {
114 this.typeSpecStack.add(type)
115 }
116
<lambda>null117 fun popType() = apply {
118 this.typeSpecStack.removeAt(typeSpecStack.size - 1)
119 }
120
emitCommentnull121 fun emitComment(codeBlock: CodeBlock) {
122 trailingNewline = true // Force the '//' prefix for the comment.
123 comment = true
124 try {
125 emitCode(codeBlock)
126 emit("\n")
127 } finally {
128 comment = false
129 }
130 }
131
emitKdocnull132 fun emitKdoc(kdocCodeBlock: CodeBlock) {
133 if (kdocCodeBlock.isEmpty()) return
134
135 emit("/**\n")
136 kdoc = true
137 try {
138 emitCode(kdocCodeBlock, ensureTrailingNewline = true)
139 } finally {
140 kdoc = false
141 }
142 emit(" */\n")
143 }
144
emitAnnotationsnull145 fun emitAnnotations(annotations: List<AnnotationSpec>, inline: Boolean) {
146 for (annotationSpec in annotations) {
147 annotationSpec.emit(this, inline)
148 emit(if (inline) " " else "\n")
149 }
150 }
151
152 /**
153 * Emits `modifiers` in the standard order. Modifiers in `implicitModifiers` will not
154 * be emitted except for [KModifier.PUBLIC]
155 */
emitModifiersnull156 fun emitModifiers(
157 modifiers: Set<KModifier>,
158 implicitModifiers: Set<KModifier> = emptySet(),
159 ) {
160 if (shouldEmitPublicModifier(modifiers, implicitModifiers)) {
161 emit(KModifier.PUBLIC.keyword)
162 emit(" ")
163 }
164 val uniqueNonPublicExplicitOnlyModifiers =
165 modifiers
166 .filterNot { it == KModifier.PUBLIC }
167 .filterNot { implicitModifiers.contains(it) }
168 .toEnumSet()
169 for (modifier in uniqueNonPublicExplicitOnlyModifiers) {
170 emit(modifier.keyword)
171 emit(" ")
172 }
173 }
174
175 /**
176 * Emits the `context` block for [contextReceivers].
177 */
emitContextReceiversnull178 fun emitContextReceivers(contextReceivers: List<TypeName>, suffix: String = "") {
179 if (contextReceivers.isNotEmpty()) {
180 val receivers = contextReceivers
181 .map { CodeBlock.of("%T", it) }
182 .joinToCode(prefix = "context(", suffix = ")")
183 emitCode(receivers)
184 emit(suffix)
185 }
186 }
187
188 /**
189 * Emit type variables with their bounds. If a type variable has more than a single bound - call
190 * [emitWhereBlock] with same input to produce an additional `where` block.
191 *
192 * This should only be used when declaring type variables; everywhere else bounds are omitted.
193 */
emitTypeVariablesnull194 fun emitTypeVariables(typeVariables: List<TypeVariableName>) {
195 if (typeVariables.isEmpty()) return
196
197 emit("<")
198 typeVariables.forEachIndexed { index, typeVariable ->
199 if (index > 0) emit(", ")
200 if (typeVariable.variance != null) {
201 emit("${typeVariable.variance.keyword} ")
202 }
203 if (typeVariable.isReified) {
204 emit("reified ")
205 }
206 emitCode("%L", typeVariable.name)
207 if (typeVariable.bounds.size == 1 && typeVariable.bounds[0] != NULLABLE_ANY) {
208 emitCode(" : %T", typeVariable.bounds[0])
209 }
210 }
211 emit(">")
212 }
213
214 /**
215 * Emit a `where` block containing type bounds for each type variable that has at least two
216 * bounds.
217 */
emitWhereBlocknull218 fun emitWhereBlock(typeVariables: List<TypeVariableName>) {
219 if (typeVariables.isEmpty()) return
220
221 var firstBound = true
222 for (typeVariable in typeVariables) {
223 if (typeVariable.bounds.size > 1) {
224 for (bound in typeVariable.bounds) {
225 if (!firstBound) emitCode(", ") else emitCode(" where ")
226 emitCode("%L : %T", typeVariable.name, bound)
227 firstBound = false
228 }
229 }
230 }
231 }
232
emitCodenull233 fun emitCode(s: String) = emitCode(CodeBlock.of(s))
234
235 fun emitCode(format: String, vararg args: Any?) = emitCode(CodeBlock.of(format, *args))
236
237 fun emitCode(
238 codeBlock: CodeBlock,
239 isConstantContext: Boolean = false,
240 ensureTrailingNewline: Boolean = false,
241 ) = apply {
242 var a = 0
243 var deferredTypeName: ClassName? = null // used by "import static" logic
244 val partIterator = codeBlock.formatParts.listIterator()
245 while (partIterator.hasNext()) {
246 when (val part = partIterator.next()) {
247 "%L" -> emitLiteral(codeBlock.args[a++], isConstantContext)
248
249 "%N" -> emit(codeBlock.args[a++] as String)
250
251 "%S" -> {
252 val string = codeBlock.args[a++] as String?
253 // Emit null as a literal null: no quotes.
254 val literal = if (string != null) {
255 stringLiteralWithQuotes(
256 string,
257 isInsideRawString = false,
258 isConstantContext = isConstantContext,
259 )
260 } else {
261 "null"
262 }
263 emit(literal, nonWrapping = true)
264 }
265
266 "%P" -> {
267 val string = codeBlock.args[a++]?.let { arg ->
268 if (arg is CodeBlock) {
269 arg.toString(this@CodeWriter)
270 } else {
271 arg as String?
272 }
273 }
274 // Emit null as a literal null: no quotes.
275 val literal = if (string != null) {
276 stringLiteralWithQuotes(
277 string,
278 isInsideRawString = true,
279 isConstantContext = isConstantContext,
280 )
281 } else {
282 "null"
283 }
284 emit(literal, nonWrapping = true)
285 }
286
287 "%T" -> {
288 var typeName = codeBlock.args[a++] as TypeName
289 if (typeName.isAnnotated) {
290 typeName.emitAnnotations(this)
291 typeName = typeName.copy(annotations = emptyList())
292 }
293 // defer "typeName.emit(this)" if next format part will be handled by the default case
294 var defer = false
295 if (typeName is ClassName && partIterator.hasNext()) {
296 if (!codeBlock.formatParts[partIterator.nextIndex()].startsWith("%")) {
297 val candidate = typeName
298 if (candidate.canonicalName in memberImportNames) {
299 check(deferredTypeName == null) { "pending type for static import?!" }
300 deferredTypeName = candidate
301 defer = true
302 }
303 }
304 }
305 if (!defer) typeName.emit(this)
306 typeName.emitNullable(this)
307 }
308
309 "%M" -> {
310 val memberName = codeBlock.args[a++] as MemberName
311 memberName.emit(this)
312 }
313
314 "%%" -> emit("%")
315
316 "⇥" -> indent()
317
318 "⇤" -> unindent()
319
320 "«" -> {
321 check(statementLine == -1) {
322 """
323 |Can't open a new statement until the current statement is closed (opening « followed
324 |by another « without a closing »).
325 |Current code block:
326 |- Format parts: ${codeBlock.formatParts.map(::escapeCharacterLiterals)}
327 |- Arguments: ${codeBlock.args}
328 |
329 """.trimMargin()
330 }
331 statementLine = 0
332 }
333
334 "»" -> {
335 check(statementLine != -1) {
336 """
337 |Can't close a statement that hasn't been opened (closing » is not preceded by an
338 |opening «).
339 |Current code block:
340 |- Format parts: ${codeBlock.formatParts.map(::escapeCharacterLiterals)}
341 |- Arguments: ${codeBlock.args}
342 |
343 """.trimMargin()
344 }
345 if (statementLine > 0) {
346 unindent(2) // End a multi-line statement. Decrease the indentation level.
347 }
348 statementLine = -1
349 }
350
351 else -> {
352 // Handle deferred type.
353 var doBreak = false
354 if (deferredTypeName != null) {
355 if (part.startsWith(".")) {
356 if (emitStaticImportMember(deferredTypeName.canonicalName, part)) {
357 // Okay, static import hit and all was emitted, so clean-up and jump to next part.
358 deferredTypeName = null
359 doBreak = true
360 }
361 }
362 if (!doBreak) {
363 deferredTypeName!!.emit(this)
364 deferredTypeName = null
365 }
366 }
367 if (!doBreak) {
368 emit(part)
369 }
370 }
371 }
372 }
373 if (ensureTrailingNewline && out.hasPendingSegments) {
374 emit("\n")
375 }
376 }
377
emitStaticImportMembernull378 private fun emitStaticImportMember(canonical: String, part: String): Boolean {
379 val partWithoutLeadingDot = part.substring(1)
380 if (partWithoutLeadingDot.isEmpty()) return false
381 val first = partWithoutLeadingDot[0]
382 if (!Character.isJavaIdentifierStart(first)) return false
383 val explicit = imports[canonical + "." + extractMemberName(partWithoutLeadingDot)]
384 if (explicit != null) {
385 if (explicit.alias != null) {
386 val memberName = extractMemberName(partWithoutLeadingDot)
387 emit(partWithoutLeadingDot.replaceFirst(memberName, explicit.alias))
388 } else {
389 emit(partWithoutLeadingDot)
390 }
391 return true
392 }
393 return false
394 }
395
emitLiteralnull396 private fun emitLiteral(o: Any?, isConstantContext: Boolean) {
397 when (o) {
398 is TypeSpec -> o.emit(this, null)
399 is AnnotationSpec -> o.emit(this, inline = true, asParameter = isConstantContext)
400 is PropertySpec -> o.emit(this, emptySet())
401 is FunSpec -> o.emit(
402 codeWriter = this,
403 enclosingName = null,
404 implicitModifiers = setOf(KModifier.PUBLIC),
405 includeKdocTags = true,
406 )
407 is TypeAliasSpec -> o.emit(this)
408 is CodeBlock -> emitCode(o, isConstantContext = isConstantContext)
409 else -> emit(o.toString())
410 }
411 }
412
413 /**
414 * Returns the best name to identify `className` with in the current context. This uses the
415 * available imports and the current scope to find the shortest name available. It does not honor
416 * names visible due to inheritance.
417 */
lookupNamenull418 fun lookupName(className: ClassName): String {
419 // Find the shortest suffix of className that resolves to className. This uses both local type
420 // names (so `Entry` in `Map` refers to `Map.Entry`). Also uses imports.
421 var nameResolved = false
422 var c: ClassName? = className
423 while (c != null) {
424 val alias = imports[c.canonicalName]?.alias
425 val simpleName = alias ?: c.simpleName
426 val resolved = resolve(simpleName)
427 nameResolved = resolved != null
428
429 // We don't care about nullability and type annotations here, as it's irrelevant for imports.
430 if (resolved == c.copy(nullable = false, annotations = emptyList())) {
431 if (alias != null) return alias
432 val suffixOffset = c.simpleNames.size - 1
433 referencedNames.add(className.topLevelClassName().simpleName)
434 return className.simpleNames.subList(
435 suffixOffset,
436 className.simpleNames.size,
437 ).joinToString(".")
438 }
439 c = c.enclosingClassName()
440 }
441
442 // If the name resolved but wasn't a match, we're stuck with the fully qualified name.
443 if (nameResolved) {
444 return className.canonicalName
445 }
446
447 // If the class is in the same package, we're done.
448 if (packageName == className.packageName) {
449 referencedNames.add(className.topLevelClassName().simpleName)
450 return className.simpleNames.joinToString(".")
451 }
452
453 // We'll have to use the fully-qualified name. Mark the type as importable for a future pass.
454 if (!kdoc) {
455 importableType(className)
456 }
457
458 return className.canonicalName
459 }
460
lookupNamenull461 fun lookupName(memberName: MemberName): String {
462 val simpleName = imports[memberName.canonicalName]?.alias ?: memberName.simpleName
463 // Match an imported member.
464 val importedMember = importedMembers[simpleName]
465 if (importedMember == memberName) {
466 return simpleName
467 } else if (importedMember != null && memberName.enclosingClassName != null) {
468 val enclosingClassName = lookupName(memberName.enclosingClassName)
469 return "$enclosingClassName.$simpleName"
470 }
471
472 // If the member is in the same package, we're done.
473 if (packageName == memberName.packageName && memberName.enclosingClassName == null) {
474 referencedNames.add(memberName.simpleName)
475 return memberName.simpleName
476 }
477
478 // We'll have to use the fully-qualified name.
479 // Mark the member as importable for a future pass unless the name clashes with
480 // a method in the current context
481 if (!kdoc && (
482 memberName.isExtension ||
483 !isMethodNameUsedInCurrentContext(memberName.simpleName)
484 )
485 ) {
486 importableMember(memberName)
487 }
488
489 return memberName.canonicalName
490 }
491
492 // TODO(luqasn): also honor superclass members when resolving names.
isMethodNameUsedInCurrentContextnull493 private fun isMethodNameUsedInCurrentContext(simpleName: String): Boolean {
494 for (it in typeSpecStack.reversed()) {
495 if (it.funSpecs.any { it.name == simpleName }) {
496 return true
497 }
498 if (!it.modifiers.contains(KModifier.INNER)) {
499 break
500 }
501 }
502 return false
503 }
504
importableTypenull505 private fun importableType(className: ClassName) {
506 val topLevelClassName = className.topLevelClassName()
507 val simpleName = imports[className.canonicalName]?.alias ?: topLevelClassName.simpleName
508 // Check for name clashes with members.
509 if (simpleName !in importableMembers) {
510 importableTypes[simpleName] = importableTypes.getValue(simpleName) + topLevelClassName
511 }
512 }
513
importableMembernull514 private fun importableMember(memberName: MemberName) {
515 if (memberName.packageName.isNotEmpty()) {
516 val simpleName = imports[memberName.canonicalName]?.alias ?: memberName.simpleName
517 // Check for name clashes with types.
518 if (simpleName !in importableTypes) {
519 importableMembers[simpleName] = importableMembers.getValue(simpleName) + memberName
520 }
521 }
522 }
523
524 /**
525 * Returns the class or enum value referenced by `simpleName`, using the current nesting context and
526 * imports.
527 */
528 // TODO(jwilson): also honor superclass members when resolving names.
resolvenull529 private fun resolve(simpleName: String): ClassName? {
530 // Match a child of the current (potentially nested) class.
531 for (i in typeSpecStack.indices.reversed()) {
532 val typeSpec = typeSpecStack[i]
533 if (simpleName in typeSpec.nestedTypesSimpleNames) {
534 return stackClassName(i, simpleName)
535 }
536 }
537
538 if (typeSpecStack.size > 0) {
539 val typeSpec = typeSpecStack[0]
540 if (typeSpec.name == simpleName) {
541 // Match the top-level class.
542 return ClassName(packageName, simpleName)
543 }
544 if (typeSpec.isEnum && typeSpec.enumConstants.keys.contains(simpleName)) {
545 // Match a top level enum value.
546 // Enum values are not proper classes but can still be modeled using ClassName.
547 return ClassName(packageName, typeSpec.name!!).nestedClass(simpleName)
548 }
549 }
550
551 // Match an imported type.
552 val importedType = importedTypes[simpleName]
553 if (importedType != null) return importedType
554
555 // No match.
556 return null
557 }
558
559 /** Returns the class named `simpleName` when nested in the class at `stackDepth`. */
stackClassNamenull560 private fun stackClassName(stackDepth: Int, simpleName: String): ClassName {
561 var className = ClassName(packageName, typeSpecStack[0].name!!)
562 for (i in 1..stackDepth) {
563 className = className.nestedClass(typeSpecStack[i].name!!)
564 }
565 return className.nestedClass(simpleName)
566 }
567
568 /**
569 * Emits `s` with indentation as required. It's important that all code that writes to
570 * [CodeWriter.out] does it through here, since we emit indentation lazily in order to avoid
571 * unnecessary trailing whitespace.
572 */
<lambda>null573 fun emit(s: String, nonWrapping: Boolean = false) = apply {
574 var first = true
575 for (line in s.split('\n')) {
576 // Emit a newline character. Make sure blank lines in KDoc & comments look good.
577 if (!first) {
578 if ((kdoc || comment) && trailingNewline) {
579 emitIndentation()
580 out.appendNonWrapping(if (kdoc) " *" else "//")
581 }
582 out.newline()
583 trailingNewline = true
584 if (statementLine != -1) {
585 if (statementLine == 0) {
586 indent(2) // Begin multiple-line statement. Increase the indentation level.
587 }
588 statementLine++
589 }
590 }
591
592 first = false
593 if (line.isEmpty()) continue // Don't indent empty lines.
594
595 // Emit indentation and comment prefix if necessary.
596 if (trailingNewline) {
597 emitIndentation()
598 if (kdoc) {
599 out.appendNonWrapping(" * ")
600 } else if (comment) {
601 out.appendNonWrapping("// ")
602 }
603 }
604
605 if (nonWrapping) {
606 out.appendNonWrapping(line)
607 } else {
608 out.append(
609 line,
610 indentLevel = if (kdoc) indentLevel else indentLevel + 2,
611 linePrefix = if (kdoc) " * " else "",
612 )
613 }
614 trailingNewline = false
615 }
616 }
617
emitIndentationnull618 private fun emitIndentation() {
619 for (j in 0 until indentLevel) {
620 out.appendNonWrapping(indent)
621 }
622 }
623
624 /**
625 * Returns whether a [KModifier.PUBLIC] should be emitted.
626 *
627 * If [modifiers] contains [KModifier.PUBLIC], this method always returns `true`.
628 *
629 * Otherwise, this will return `true` when [KModifier.PUBLIC] is one of the [implicitModifiers]
630 * and there are no other opposing modifiers (like [KModifier.PROTECTED] etc.) supplied by the
631 * consumer in [modifiers].
632 */
shouldEmitPublicModifiernull633 private fun shouldEmitPublicModifier(
634 modifiers: Set<KModifier>,
635 implicitModifiers: Set<KModifier>,
636 ): Boolean {
637 if (modifiers.contains(KModifier.PUBLIC)) {
638 return true
639 }
640
641 if (!implicitModifiers.contains(KModifier.PUBLIC)) {
642 return false
643 }
644
645 val hasOtherConsumerSpecifiedVisibility =
646 modifiers.containsAnyOf(KModifier.PRIVATE, KModifier.INTERNAL, KModifier.PROTECTED)
647
648 return !hasOtherConsumerSpecifiedVisibility
649 }
650
651 /**
652 * Returns the types that should have been imported for this code. If there were any simple name
653 * collisions, import aliases will be generated.
654 */
suggestedTypeImportsnull655 private fun suggestedTypeImports(): Map<String, Set<ClassName>> {
656 return importableTypes.filterKeys { it !in referencedNames }.mapValues { it.value.toSet() }
657 }
658
659 /**
660 * Returns the members that should have been imported for this code. If there were any simple name
661 * collisions, import aliases will be generated.
662 */
suggestedMemberImportsnull663 private fun suggestedMemberImports(): Map<String, Set<MemberName>> {
664 return importableMembers.filterKeys { it !in referencedNames }.mapValues { it.value.toSet() }
665 }
666
667 /**
668 * Perform emitting actions on the current [CodeWriter] using a custom [Appendable]. The
669 * [CodeWriter] will continue using the old [Appendable] after this method returns.
670 */
emitIntonull671 inline fun emitInto(out: Appendable, action: CodeWriter.() -> Unit) {
672 val codeWrapper = this
673 LineWrapper(out, indent = DEFAULT_INDENT, columnLimit = Int.MAX_VALUE).use { newOut ->
674 val oldOut = codeWrapper.out
675 codeWrapper.out = newOut
676 action()
677 codeWrapper.out = oldOut
678 }
679 }
680
closenull681 override fun close() {
682 out.close()
683 }
684
685 companion object {
686 /**
687 * Makes a pass to collect imports by executing [emitStep], and returns an instance of
688 * [CodeWriter] pre-initialized with collected imports.
689 */
withCollectedImportsnull690 fun withCollectedImports(
691 out: Appendable,
692 indent: String,
693 memberImports: Map<String, Import>,
694 emitStep: (importsCollector: CodeWriter) -> Unit,
695 ): CodeWriter {
696 // First pass: emit the entire class, just to collect the types we'll need to import.
697 val importsCollector = CodeWriter(
698 NullAppendable,
699 indent,
700 memberImports,
701 columnLimit = Integer.MAX_VALUE,
702 )
703 emitStep(importsCollector)
704 val generatedImports = mutableMapOf<String, Import>()
705 val suggestedTypeImports = importsCollector.suggestedTypeImports()
706 .generateImports(
707 generatedImports,
708 canonicalName = ClassName::canonicalName,
709 packageName = ClassName::packageName,
710 capitalizeAliases = true,
711 )
712 val suggestedMemberImports = importsCollector.suggestedMemberImports()
713 .generateImports(
714 generatedImports,
715 canonicalName = MemberName::canonicalName,
716 packageName = MemberName::packageName,
717 capitalizeAliases = false,
718 )
719 importsCollector.close()
720
721 return CodeWriter(
722 out,
723 indent,
724 memberImports + generatedImports.filterKeys { it !in memberImports },
725 suggestedTypeImports,
726 suggestedMemberImports,
727 )
728 }
729
generateImportsnull730 private fun <T> Map<String, Set<T>>.generateImports(
731 generatedImports: MutableMap<String, Import>,
732 canonicalName: T.() -> String,
733 packageName: T.() -> String,
734 capitalizeAliases: Boolean,
735 ): Map<String, T> {
736 return flatMap { (simpleName, qualifiedNames) ->
737 if (qualifiedNames.size == 1) {
738 listOf(simpleName to qualifiedNames.first()).also {
739 val canonicalName = qualifiedNames.first().canonicalName()
740 generatedImports[canonicalName] = Import(canonicalName)
741 }
742 } else {
743 generateImportAliases(simpleName, qualifiedNames, packageName, capitalizeAliases)
744 .onEach { (alias, qualifiedName) ->
745 val canonicalName = qualifiedName.canonicalName()
746 generatedImports[canonicalName] = Import(canonicalName, alias)
747 }
748 }
749 }.toMap()
750 }
751
generateImportAliasesnull752 private fun <T> generateImportAliases(
753 simpleName: String,
754 qualifiedNames: Set<T>,
755 packageName: T.() -> String,
756 capitalizeAliases: Boolean,
757 ): List<Pair<String, T>> {
758 val packageNameSegments = qualifiedNames.associateWith { qualifiedName ->
759 qualifiedName.packageName().split('.').map { it.replaceFirstChar(Char::uppercaseChar) }
760 }
761 val aliasNames = mutableMapOf<String, T>()
762 var segmentsToUse = 0
763 // Iterate until we have unique aliases for all names.
764 while (aliasNames.size != qualifiedNames.size) {
765 segmentsToUse += 1
766 aliasNames.clear()
767 for ((qualifiedName, segments) in packageNameSegments) {
768 val aliasPrefix = segments.takeLast(min(segmentsToUse, segments.size))
769 .joinToString(separator = "")
770 .replaceFirstChar { if (!capitalizeAliases) it.lowercaseChar() else it }
771 val aliasName = aliasPrefix + simpleName.replaceFirstChar(Char::uppercaseChar)
772 aliasNames[aliasName] = qualifiedName
773 }
774 }
775 return aliasNames.toList()
776 }
777 }
778 }
779