• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright 2020 Google LLC
3  * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.google.devtools.ksp.processing.impl
19 
20 import com.google.devtools.ksp.NoSourceFile
21 import com.google.devtools.ksp.processing.CodeGenerator
22 import com.google.devtools.ksp.processing.Dependencies
23 import com.google.devtools.ksp.symbol.KSClassDeclaration
24 import com.google.devtools.ksp.symbol.KSFile
25 import java.io.File
26 import java.io.FileOutputStream
27 import java.io.IOException
28 import java.io.OutputStream
29 
30 class CodeGeneratorImpl(
31     private val classDir: File,
32     private val javaDir: File,
33     private val kotlinDir: File,
34     private val resourcesDir: File,
35     private val projectBase: File,
36     private val anyChangesWildcard: KSFile,
37     private val allSources: List<KSFile>,
38     private val isIncremental: Boolean
39 ) : CodeGenerator {
40     private val fileMap = mutableMapOf<String, File>()
41     private val fileOutputStreamMap = mutableMapOf<String, FileOutputStream>()
42 
43     private val separator = File.separator
44 
45     val sourceToOutputs: MutableMap<File, MutableSet<File>> = mutableMapOf()
46 
47     // This function will also clear `fileOutputStreamMap` which will change the result of `generatedFile`
48     fun closeFiles() {
49         fileOutputStreamMap.keys.forEach {
50             fileOutputStreamMap[it]!!.close()
51         }
52         fileOutputStreamMap.clear()
53     }
54 
55     fun pathOf(packageName: String, fileName: String, extensionName: String): String {
56         val packageDirs = if (packageName != "") "${packageName.split(".").joinToString(separator)}$separator" else ""
57         val extension = if (extensionName != "") ".$extensionName" else ""
58         return "$packageDirs$fileName$extension"
59     }
60 
61     override fun createNewFile(
62         dependencies: Dependencies,
63         packageName: String,
64         fileName: String,
65         extensionName: String
66     ): OutputStream {
67         return createNewFile(
68             dependencies,
69             pathOf(packageName, fileName, extensionName),
70             extensionToDirectory(extensionName)
71         )
72     }
73 
74     override fun createNewFileByPath(dependencies: Dependencies, path: String, extensionName: String): OutputStream {
75         val extension = if (extensionName != "") ".$extensionName" else ""
76         return createNewFile(dependencies, path + extension, extensionToDirectory(extensionName))
77     }
78 
79     override fun associate(sources: List<KSFile>, packageName: String, fileName: String, extensionName: String) {
80         associate(sources, pathOf(packageName, fileName, extensionName), extensionToDirectory(extensionName))
81     }
82 
83     override fun associateByPath(sources: List<KSFile>, path: String, extensionName: String) {
84         val extension = if (extensionName != "") ".$extensionName" else ""
85         associate(sources, path + extension, extensionToDirectory(extensionName))
86     }
87 
88     override fun associateWithClasses(
89         classes: List<KSClassDeclaration>,
90         packageName: String,
91         fileName: String,
92         extensionName: String
93     ) {
94         val path = pathOf(packageName, fileName, extensionName)
95         val files = classes.map {
96             it.containingFile ?: NoSourceFile(projectBase, it.qualifiedName?.asString().toString())
97         }
98         associate(files, path, extensionToDirectory(extensionName))
99     }
100 
101     private fun extensionToDirectory(extensionName: String): File {
102         return when (extensionName) {
103             "class" -> classDir
104             "java" -> javaDir
105             "kt" -> kotlinDir
106             else -> resourcesDir
107         }
108     }
109 
110     private fun createNewFile(dependencies: Dependencies, path: String, baseDir: File): OutputStream {
111         val file = File(baseDir, path)
112         if (!isWithinBaseDir(baseDir, file)) {
113             throw IllegalStateException("requested path is outside the bounds of the required directory")
114         }
115         val absolutePath = file.absolutePath
116         if (absolutePath in fileMap) {
117             throw FileAlreadyExistsException(file)
118         }
119         val parentFile = file.parentFile
120         if (!parentFile.exists() && !parentFile.mkdirs()) {
121             throw IllegalStateException("failed to make parent directories.")
122         }
123         file.writeText("")
124         fileMap[absolutePath] = file
125         val sources = if (dependencies.isAllSources) {
126             allSources + anyChangesWildcard
127         } else {
128             if (dependencies.aggregating) {
129                 dependencies.originatingFiles + anyChangesWildcard
130             } else {
131                 dependencies.originatingFiles
132             }
133         }
134         associate(sources, file)
135         fileOutputStreamMap[absolutePath] = fileMap[absolutePath]!!.outputStream()
136         return fileOutputStreamMap[absolutePath]!!
137     }
138 
139     private fun isWithinBaseDir(baseDir: File, file: File): Boolean {
140         val base = baseDir.toPath().normalize()
141         return try {
142             val relativePath = file.toPath().normalize()
143             relativePath.startsWith(base)
144         } catch (e: IOException) {
145             false
146         }
147     }
148 
149     private fun associate(sources: List<KSFile>, path: String, baseDir: File) {
150         val file = File(baseDir, path)
151         if (!isWithinBaseDir(baseDir, file)) {
152             throw IllegalStateException("requested path is outside the bounds of the required directory")
153         }
154         associate(sources, file)
155     }
156 
157     private fun associate(sources: List<KSFile>, outputPath: File) {
158         if (!isIncremental)
159             return
160 
161         val output = outputPath.relativeTo(projectBase)
162         sources.forEach { source ->
163             sourceToOutputs.getOrPut(File(source.filePath).relativeTo(projectBase)) { mutableSetOf() }.add(output)
164         }
165     }
166 
167     val outputs: Set<File>
168         get() = fileMap.values.mapTo(mutableSetOf()) { it.relativeTo(projectBase) }
169 
170     override val generatedFile: Collection<File>
171         get() = fileOutputStreamMap.keys.map { fileMap[it]!! }
172 }
173