Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
*/
package org.domaframework.doma.intellij.common

import com.intellij.compiler.CompilerConfiguration
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.jps.model.java.JavaResourceRootType
Expand All @@ -32,31 +34,47 @@ object CommonPathParameterUtil {
/**
* Holds directory information for a module.
*
* @property moduleBasePath The base path of the module.
* @property moduleSourceDirectories List of source directories.
* @property moduleResourceDirectories List of resource directories.
* @property moduleTestSourceDirectories List of test source directories.
* @property moduleTestResourceDirectories List of test resource directories.
*/
data class ModulePaths(
val moduleBasePath: VirtualFile?,
val moduleSourceDirectories: List<VirtualFile>,
val moduleResourceDirectories: List<VirtualFile>,
val moduleTestSourceDirectories: List<VirtualFile>,
val moduleTestResourceDirectories: List<VirtualFile>,
)

// Cache for each module's directory information.
private val modulePathCache = ConcurrentHashMap<Module, ModulePaths>()
private val modulePathCache = ConcurrentHashMap<String, ModulePaths>()

/**
* Returns the directory information for the specified module (uses cache if available).
* If the module's directory structure has changed, call [refreshModulePaths] to update the cache.
*
* @param module The module to retrieve directory information for.
* @return The cached or newly computed ModulePaths.
*/
fun getModulePaths(module: Module): ModulePaths = modulePathCache[module] ?: refreshModulePaths(module)
fun getModulePaths(module: Module): ModulePaths? = modulePathCache[module.name]

/**
* Checks if a given path is a generated directory based on annotation processor settings.
*
* @param module The module to check.
* @param path The path to check.
* @return True if the path is a generated directory, false otherwise.
*/
private fun isGeneratedDirectory(
module: Module,
path: String,
): Boolean {
val project: Project = module.project
val compilerConfiguration = CompilerConfiguration.getInstance(project).getAnnotationProcessingConfiguration(module)
val annotationProcessingConfiguration = compilerConfiguration.getGeneratedSourcesDirectoryName(false)

// Check if the path matches any of the generated source directories
return path.contains("/build/$annotationProcessingConfiguration/")
}

/**
* Refreshes the directory information for the specified module and updates the cache.
Expand All @@ -65,38 +83,53 @@ object CommonPathParameterUtil {
* @param module The module to refresh.
* @return The updated ModulePaths.
*/
fun refreshModulePaths(module: Module): ModulePaths {
var basePath: VirtualFile? = null
fun refreshModulePaths(module: Module) {
val sourceDirs = mutableListOf<VirtualFile>()
val resourceDirs = mutableListOf<VirtualFile>()
val testSourceDirs = mutableListOf<VirtualFile>()
val testResourceDirs = mutableListOf<VirtualFile>()

val moduleManager = ModuleRootManager.getInstance(module)
moduleManager.contentEntries.firstOrNull()?.let { entry ->
basePath = entry.file
entry.sourceFolders.forEach { folder ->
val file = folder.file
if (file != null) {
when (folder.rootType) {
JavaSourceRootType.SOURCE -> sourceDirs.add(file)
JavaSourceRootType.TEST_SOURCE -> testSourceDirs.add(file)
JavaResourceRootType.RESOURCE -> resourceDirs.add(file)
JavaResourceRootType.TEST_RESOURCE -> testResourceDirs.add(file)
moduleManager.contentEntries.forEach { entry ->
val entryFile = entry.file
if (entryFile != null && !isGeneratedDirectory(module, entryFile.path)) {
entry.sourceFolders.forEach { folder ->
val file = folder.file
if (file != null) {
when (folder.rootType) {
JavaSourceRootType.SOURCE ->
if (!sourceDirs.contains(file)) {
sourceDirs.add(file)
}

JavaSourceRootType.TEST_SOURCE ->
if (!testSourceDirs.contains(file)) {
testSourceDirs.add(file)
}

JavaResourceRootType.RESOURCE ->
if (!resourceDirs.contains(file)) {
resourceDirs.add(file)
}

JavaResourceRootType.TEST_RESOURCE ->
if (!testResourceDirs.contains(file)) {
testResourceDirs.add(file)
}
}
}
}
}
}

val paths =
ModulePaths(
basePath,
sourceDirs,
resourceDirs,
testSourceDirs,
testResourceDirs,
)
modulePathCache[module] = paths
return paths
modulePathCache[module.name] = paths
}

/**
Expand All @@ -110,7 +143,7 @@ object CommonPathParameterUtil {
module: Module,
file: VirtualFile,
): Boolean {
val paths = getModulePaths(module)
val paths = getModulePaths(module) ?: return false
if (paths.moduleTestSourceDirectories.any { file.path.contains(it.path) }) return true
if (paths.moduleTestResourceDirectories.any { file.path.contains(it.path) }) return true
return false
Expand All @@ -127,30 +160,14 @@ object CommonPathParameterUtil {
fun getResources(
module: Module,
file: VirtualFile,
): List<VirtualFile> =
if (isTest(module, file)) {
getModulePaths(module).moduleTestResourceDirectories
} else {
getModulePaths(module).moduleResourceDirectories
}

/**
* Returns the source directories for the given file in the specified module.
* If the file is in a test directory, test source directories are returned.
*
* @param module The module to check.
* @param file The file to check.
* @return List of source directories.
*/
fun getSources(
module: Module,
file: VirtualFile,
): List<VirtualFile> =
if (isTest(module, file)) {
getModulePaths(module).moduleTestSourceDirectories
): List<VirtualFile> {
val modulePaths = getModulePaths(module) ?: return emptyList()
return if (isTest(module, file)) {
modulePaths.moduleTestResourceDirectories
} else {
getModulePaths(module).moduleSourceDirectories
modulePaths.moduleResourceDirectories
}
}

/**
* Clears the module directory cache. Call this if the module structure changes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,10 @@ import org.domaframework.doma.intellij.common.getJarRoot
import org.domaframework.doma.intellij.common.getMethodDaoFilePath
import org.domaframework.doma.intellij.common.isInjectionSqlFile
import org.domaframework.doma.intellij.common.isSupportFileType
import org.domaframework.doma.intellij.common.sourceExtensionNames
import org.domaframework.doma.intellij.extension.findFile
import org.domaframework.doma.intellij.extension.getContentRoot
import org.domaframework.doma.intellij.extension.getJavaClazz
import org.domaframework.doma.intellij.extension.getModule
import org.domaframework.doma.intellij.extension.getSourceRootDir
import org.jetbrains.kotlin.idea.base.util.module

/**
Expand All @@ -57,36 +56,24 @@ fun findDaoMethod(
return PsiTreeUtil.getParentOfType(originalFile.context, PsiMethod::class.java)
}
} else if (isSupportFileType(originalFile)) {
// TODO: Add Support Kotlin
val methodName = virtualFile.nameWithoutExtension
val daoFile = daoFile ?: findDaoFile(project, originalFile) ?: return null
if (module != null) {
val relativePath =
val contentRootPath = project.getContentRoot(virtualFile)?.path ?: return null
val daoJavaFile =
getDaoPathFromSqlFilePath(
originalFile,
project.getContentRoot(virtualFile)?.path ?: "",
)
// get ClassPath with package name
val daoClassName: String =
relativePath
.substringBefore(".")
.replace("/", ".")
.replace("\\", ".")
.replace("..", ".")
.trim('.')
contentRootPath,
) ?: return null

val daoJavaFile = project.findFile(daoFile)
val isTest = CommonPathParameterUtil.isTest(module, originalFile.virtualFile)
findDaoClass(module, isTest, daoClassName)
?.let { daoClass ->
val daoMethod =
// TODO Support Kotlin Project
when (daoJavaFile) {
is PsiJavaFile -> findUseSqlDaoMethod(daoJavaFile, methodName)
else -> null
}
return daoMethod
// TODO Support Kotlin Project
val daoFile = daoJavaFile.containingFile
val daoMethod =
when (daoFile) {
is PsiJavaFile -> findUseSqlDaoMethod(daoFile, methodName)
else -> null
}
return daoMethod
} else {
val fileType = getExtension(daoFile.fileType.name)
val jarRootPath = virtualFile.path.substringBefore("jar!").plus("jar!")
Expand Down Expand Up @@ -132,75 +119,36 @@ fun findDaoFile(
if (contentRoot == null) {
return getJarRoot(virtualFile, sqlFile)
}
return searchDaoFile(sqlFile.module, contentRoot, sqlFile)
return getDaoPathFromSqlFilePath(
sqlFile,
contentRoot.path,
)?.containingFile?.virtualFile
}

/**
* DAO file search for SQL file
*/
private fun searchDaoFile(
module: Module?,
contentRoot: VirtualFile?,
sqlFile: PsiFile,
): VirtualFile? {
val contentRootPath = contentRoot?.path ?: return null
val pathParams = module?.let { CommonPathParameterUtil.getModulePaths(it) } ?: return null
val moduleBaseName = pathParams.moduleBasePath?.nameWithoutExtension ?: ""
// TODO: Add Support Kotlin
val relativeDaoFilePaths =
getDaoPathFromSqlFilePath(sqlFile, contentRoot.path)
val sources = CommonPathParameterUtil.getSources(module, sqlFile.virtualFile)

if (contentRootPath.endsWith(moduleBaseName) == true) {
sources.forEach { source ->
sourceExtensionNames.forEach { extension ->
val fileExtension = getExtension(extension)
val findDaoFile =
contentRoot.findFileByRelativePath("${source.nameWithoutExtension}$relativeDaoFilePaths.$fileExtension")
if (findDaoFile != null) return findDaoFile
}
}
}
return null
}

private fun findDaoClass(
module: Module,
includeTest: Boolean,
daoClassName: String,
): PsiClass? = module.getJavaClazz(includeTest, daoClassName)

/**
* Generate DAO deployment path from SQL file path
* @param sqlFile SQL File
* @param projectRootPath project content Root Path
* @param contentRootPath project content Root Path
* @return
*/
private fun getDaoPathFromSqlFilePath(
sqlFile: PsiFile,
projectRootPath: String,
): String {
contentRootPath: String,
): PsiClass? {
if (isInjectionSqlFile(sqlFile)) {
return ""
}
val module = sqlFile.module
val sqlPath = sqlFile.virtualFile?.path ?: return ""
var relativeFilePath = sqlPath.substring(projectRootPath.length)
if (!relativeFilePath.startsWith("/")) {
relativeFilePath = "/$relativeFilePath"
return null
}
val resources =
module?.let { CommonPathParameterUtil.getResources(it, sqlFile.virtualFile) }
?: emptyList()
val module = sqlFile.module ?: return null
val sqlPath = sqlFile.virtualFile?.path ?: return null
val relativePath =
sqlPath.substringAfter(contentRootPath, "").replace("/$RESOURCES_META_INF_PATH", "")
val resourcesRootPath = module.project.getSourceRootDir(sqlFile.virtualFile)?.name ?: ""
val isTest = CommonPathParameterUtil.isTest(module, sqlFile.virtualFile)

return resources
.firstOrNull { resource ->
relativeFilePath.startsWith("/" + resource.nameWithoutExtension)
}?.let { resource ->
relativeFilePath
.replace("${resource.nameWithoutExtension}/$RESOURCES_META_INF_PATH/", "")
.replace("/${sqlFile.name}", "")
} ?: ""
val packageName = relativePath.replaceFirst("/$resourcesRootPath/", "").replace("/${sqlFile.name}", "").replace("/", ".")
val daoClassFile = module.getJavaClazz(isTest, packageName)

return daoClassFile
}

/**
Expand All @@ -215,18 +163,14 @@ fun getRelativeSqlFilePathFromDaoFilePath(
): String {
if (module == null) return ""
val extension = daoFile.fileType.defaultExtension
val pathParams = CommonPathParameterUtil.getModulePaths(module)
var relativeSqlFilePath =
daoFile.path
.replaceFirst(pathParams.moduleBasePath?.path ?: "", "")
.replace(".$extension", "")
val sources = CommonPathParameterUtil.getSources(module, daoFile)
sources.forEach { source ->
relativeSqlFilePath =
relativeSqlFilePath.replaceFirst(
"/" + source.nameWithoutExtension,
RESOURCES_META_INF_PATH,
)
}
return relativeSqlFilePath
val sourceRoot = module.project.getSourceRootDir(daoFile) ?: return ""

val project = module.project
val sourceRootParent = project.getContentRoot(daoFile) ?: return ""
val daoFilePath = daoFile.path

return daoFilePath
.substringAfter(sourceRootParent.path)
.replaceFirst("/${sourceRoot.name}", RESOURCES_META_INF_PATH)
.replace(".$extension", "")
}
Loading
Loading