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