Initial commit

This commit is contained in:
2023-09-03 01:11:31 +08:00
commit f03805ff2c
53 changed files with 4392 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 HighCapable
* https://github.com/HighCapable/SweetProperty
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is Created by fankes on 2023/8/27.
*/
@file:Suppress("unused")
package com.highcapable.sweetproperty.utils
import java.io.File
/**
* 字符串路径转换为文件
*
* 自动调用 [parseFileSeparator]
* @return [File]
*/
internal fun String.toFile() = File(parseFileSeparator())
/**
* 格式化到当前操作系统的文件分隔符
* @return [String]
*/
internal fun String.parseFileSeparator() = replace("/", File.separator).replace("\\", File.separator)
/**
* 格式化到 Unix 操作系统的文件分隔符
* @return [String]
*/
internal fun String.parseUnixFileSeparator() = replace("\\", "/")
/**
* 检查目录是否为空
*
* - 如果不是目录 (可能是文件) - 返回 true
* - 如果文件不存在 - 返回 true
* @return [Boolean]
*/
internal fun File.isEmpty() = exists().not() || isDirectory.not() || listFiles().isNullOrEmpty()
/** 删除目录下的空子目录 */
internal fun File.deleteEmptyRecursively() {
listFiles { file -> file.isDirectory }?.forEach { subDir ->
subDir.deleteEmptyRecursively()
if (subDir.listFiles()?.isEmpty() == true) subDir.delete()
}
}

View File

@@ -0,0 +1,110 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 HighCapable
* https://github.com/HighCapable/SweetProperty
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is Created by fankes on 2023/8/26.
*/
package com.highcapable.sweetproperty.utils
/**
* 当数组不为空时返回非空
* @return [T] or null
*/
internal inline fun <reified T : Collection<*>> T.noEmpty() = takeIf { it.isNotEmpty() }
/**
* 当字符串不为空白时返回非空
* @return [T] or null
*/
internal inline fun <reified T : CharSequence> T.noBlank() = takeIf { it.isNotBlank() }
/**
* 扁平化字符串处理
*
* 移除所有空格并转换为小写字母
* @return [String]
*/
internal fun String.flatted() = replace(" ", "").lowercase()
/**
* 驼峰、"-"、"." 转大写下划线命名
* @return [String]
*/
internal fun String.underscore() = replace(".", "_").replace("-", "_").replace(" ", "_").replace("([a-z])([A-Z]+)".toRegex(), "$1_$2").uppercase()
/**
* 下划线、分隔线、点、空格命名字符串转小驼峰命名字符串
* @return [String]
*/
internal fun String.camelcase() = runCatching {
split("_", ".", "-", " ").map { it.replaceFirstChar { e -> e.titlecase() } }.let { words ->
words.first().replaceFirstChar { it.lowercase() } + words.drop(1).joinToString("")
}
}.getOrNull() ?: this
/**
* 下划线、分隔线、点、空格命名字符串转大驼峰命名字符串
* @return [String]
*/
internal fun String.uppercamelcase() = camelcase().capitalize()
/**
* 字符串首字母大写
* @return [String]
*/
internal fun String.capitalize() = replaceFirstChar { it.uppercaseChar() }
/**
* 字符串首字母小写
* @return [String]
*/
internal fun String.uncapitalize() = replaceFirstChar { it.lowercaseChar() }
/**
* 转换字符串第一位数字到外观近似大写字母
* @return [String]
*/
internal fun String.firstNumberToLetter() =
if (isNotBlank()) (mapOf(
'0' to 'O', '1' to 'I',
'2' to 'Z', '3' to 'E',
'4' to 'A', '5' to 'S',
'6' to 'G', '7' to 'T',
'8' to 'B', '9' to 'P'
)[first()] ?: first()) + substring(1)
else this
/**
* 转换字符串为非 Java 关键方法引用名称
* @return [String]
*/
internal fun String.toNonJavaName() = if (lowercase() == "class") replace("lass", "lazz") else this
/**
* 字符串中是否存在插值符号 ${...}
* @return [Boolean]
*/
internal fun String.hasInterpolation() = contains("\${") && contains("}")
/**
* 替换字符串中的插值符号 ${...}
* @param result 回调结果
* @return [String]
*/
internal fun String.replaceInterpolation(result: (groupValue: String) -> CharSequence) =
"\\$\\{(.+?)}".toRegex().replace(this) { result(it.groupValues[1]) }

View File

@@ -0,0 +1,192 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 HighCapable
* https://github.com/HighCapable/SweetProperty
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is Created by fankes on 2023/8/27.
*/
package com.highcapable.sweetproperty.utils.code
import com.highcapable.sweetproperty.utils.code.entity.MavenPomData
import com.highcapable.sweetproperty.utils.debug.SError
import com.highcapable.sweetproperty.utils.deleteEmptyRecursively
import com.highcapable.sweetproperty.utils.parseFileSeparator
import com.highcapable.sweetproperty.utils.toFile
import java.io.File
import java.util.jar.JarEntry
import java.util.jar.JarOutputStream
import javax.tools.DiagnosticCollector
import javax.tools.JavaFileObject
import javax.tools.StandardLocation
import javax.tools.ToolProvider
/**
* 代码编译处理类
*/
internal object CodeCompiler {
/** Maven 模型版本 */
private const val MAVEN_MODEL_VERSION = "4.0.0"
/**
* 编译 [JavaFileObject] 为 Maven 依赖
* @param pomData Maven POM 实体
* @param outputDirPath 编译输出目录路径
* @param files [JavaFileObject] 数组
* @param compileOnlyFiles [JavaFileObject] 仅编译数组 - 默认空
* @throws IllegalStateException 如果编译失败
*/
internal fun compile(
pomData: MavenPomData,
outputDirPath: String,
files: List<JavaFileObject>,
compileOnlyFiles: List<JavaFileObject> = mutableListOf()
) {
val outputDir = outputDirPath.toFile()
if (files.isEmpty()) {
if (outputDir.exists()) outputDir.deleteRecursively()
return
} else outputDir.also { if (it.exists().not()) it.mkdirs() }
val outputBuildDir = "$outputDirPath/build".toFile().also { if (it.exists()) it.deleteRecursively(); it.mkdirs() }
val outputClassesDir = "${outputBuildDir.absolutePath}/classes".toFile().apply { mkdirs() }
val outputSourcesDir = "${outputBuildDir.absolutePath}/sources".toFile().apply { mkdirs() }
val compiler = ToolProvider.getSystemJavaCompiler()
val diagnostics = DiagnosticCollector<JavaFileObject>()
val fileManager = compiler.getStandardFileManager(diagnostics, null, null)
fileManager.setLocation(StandardLocation.CLASS_OUTPUT, listOf(outputClassesDir))
val task = compiler.getTask(null, fileManager, diagnostics, null, null, compileOnlyFiles + files)
val result = task.call()
var diagnosticsMessage = ""
diagnostics.diagnostics?.forEach { diagnostic ->
diagnosticsMessage += " > Error on line ${diagnostic.lineNumber} in ${diagnostic.source?.toUri()}\n"
diagnosticsMessage += " ${diagnostic.getMessage(null)}\n"
}
runCatching { fileManager.close() }
if (result) {
compileOnlyFiles.forEach { "${outputClassesDir.absolutePath}/${it.name}".replace(".java", ".class").toFile().delete() }
files.forEach {
it.toFiles(outputSourcesDir).also { (sourceDir, sourceFile) ->
sourceDir.mkdirs()
sourceFile.writeText(it.getCharContent(true).toString())
}
}; outputClassesDir.deleteEmptyRecursively()
writeMetaInf(outputClassesDir.absolutePath)
writeMetaInf(outputSourcesDir.absolutePath)
createJarAndPom(pomData, outputDir, outputBuildDir, outputClassesDir, outputSourcesDir)
} else SError.make("Failed to compile java files into path: $outputDirPath\n$diagnosticsMessage")
}
/**
* 打包 JAR 并写入 POM
* @param pomData Maven POM 实体
* @param outputDir 编译输出目录
* @param buildDir 编译目录
* @param classesDir 编译二进制目录
* @param sourcesDir 编译源码目录
*/
private fun createJarAndPom(pomData: MavenPomData, outputDir: File, buildDir: File, classesDir: File, sourcesDir: File) {
val pomPath = "${pomData.groupId.toPomPathName()}/${pomData.artifactId}/${pomData.version}"
val pomDir = "${outputDir.absolutePath}/$pomPath".toFile().also { if (it.exists().not()) it.mkdirs() }
packageToJar(classesDir, pomDir, pomData, isSourcesJar = false)
packageToJar(sourcesDir, pomDir, pomData, isSourcesJar = true)
writePom(pomDir.absolutePath, pomData)
buildDir.deleteRecursively()
}
/**
* 写入 META-INF/MANIFEST.MF
* @param dirPath 当前目录路径
*/
private fun writeMetaInf(dirPath: String) {
val metaInfFile = "$dirPath/META-INF".toFile().apply { mkdirs() }
"${metaInfFile.absolutePath}/MANIFEST.MF".toFile().writeText("Manifest-Version: 1.0")
}
/**
* 写入 POM
* @param dirPath 当前目录路径
* @param pomData Maven POM 实体
*/
private fun writePom(dirPath: String, pomData: MavenPomData) =
"$dirPath/${pomData.artifactId}-${pomData.version}.pom".toFile().writeText(
"""
<?xml version="1.0" encoding="UTF-8"?>
<project
xmlns="http://maven.apache.org/POM/$MAVEN_MODEL_VERSION"
xsi:schemaLocation="http://maven.apache.org/POM/$MAVEN_MODEL_VERSION https://maven.apache.org/xsd/maven-$MAVEN_MODEL_VERSION.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>$MAVEN_MODEL_VERSION</modelVersion>
<groupId>${pomData.groupId}</groupId>
<artifactId>${pomData.artifactId}</artifactId>
<version>${pomData.version}</version>
</project>
""".trimIndent()
)
/**
* 转换到 [MavenPomData] 目录名称
* @return [String]
*/
private fun String.toPomPathName() = trim().replace(".", "/").replace("_", "/").replace(":", "/").replace("-", "/")
/**
* 转换到文件
* @param outputDir 输出目录
* @return [Pair]<[File], [File]>
*/
private fun JavaFileObject.toFiles(outputDir: File): Pair<File, File> {
val outputDirPath = outputDir.absolutePath
val separator = if (name.contains("/")) "/" else "\\"
val names = name.split(separator)
val fileName = names[names.lastIndex]
val folderName = name.replace(fileName, "")
return "$outputDirPath/$folderName".toFile() to "$outputDirPath/$name".toFile()
}
/**
* 打包编译输出目录到 JAR
* @param buildDir 编译目录
* @param outputDir 输出目录
* @param pomData Maven POM 实体
* @param isSourcesJar 是否为源码 JAR
* @throws IllegalStateException 如果编译输出目录不存在
*/
private fun packageToJar(buildDir: File, outputDir: File, pomData: MavenPomData, isSourcesJar: Boolean) {
if (buildDir.exists().not()) SError.make("Jar file output path not found: ${buildDir.absolutePath}")
/**
* 添加文件到 JAR
* @param jos 当前输出流
* @param parentPath 父级路径
*/
fun File.addToJar(jos: JarOutputStream, parentPath: String = "") {
val currentPath = "$parentPath$name".replace("${buildDir.name}|", "").replace("|", "/").parseFileSeparator()
if (isFile) {
if (name.startsWith(".")) return
jos.putNextEntry(JarEntry(currentPath))
inputStream().use { fis ->
val buffer = ByteArray(4096)
var bytesRead: Int
while (fis.read(buffer).also { bytesRead = it } != -1) jos.write(buffer, 0, bytesRead)
}
jos.closeEntry()
} else listFiles()?.forEach { it.addToJar(jos, parentPath = "$currentPath|") }
}
val jarFile = "${outputDir.absolutePath}/${pomData.artifactId}-${pomData.version}${if (isSourcesJar) "-sources" else ""}.jar".toFile()
if (jarFile.exists()) jarFile.delete()
jarFile.outputStream().use { fos -> JarOutputStream(fos).use { jos -> buildDir.addToJar(jos) } }
}
}

View File

@@ -0,0 +1,30 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 HighCapable
* https://github.com/HighCapable/SweetProperty
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is Created by fankes on 2023/8/27.
*/
package com.highcapable.sweetproperty.utils.code.entity
/**
* Maven POM 实体
* @param groupId Group ID
* @param artifactId Artifact Id
* @param version 版本
*/
internal data class MavenPomData(internal val groupId: String, internal val artifactId: String, internal val version: String)

View File

@@ -0,0 +1,83 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 HighCapable
* https://github.com/HighCapable/SweetProperty
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is Created by fankes on 2023/8/27.
*/
@file:Suppress("unused")
package com.highcapable.sweetproperty.utils.code.factory
import com.highcapable.sweetproperty.utils.code.CodeCompiler
import com.highcapable.sweetproperty.utils.code.entity.MavenPomData
import com.squareup.javapoet.JavaFile
import javax.tools.JavaFileObject
/**
* 编译 [JavaFile] 为 Maven 依赖
* @param pomData Maven POM 实体
* @param outputDirPath 编译输出目录路径
* @param compileOnlyFiles [JavaFile] 仅编译数组 - 默认空
* @throws IllegalStateException 如果编译失败
*/
@JvmName("compileWithJavaFile")
internal fun JavaFile.compile(pomData: MavenPomData, outputDirPath: String, compileOnlyFiles: List<JavaFile> = mutableListOf()) =
CodeCompiler.compile(
pomData = pomData,
outputDirPath = outputDirPath,
files = listOf(toJavaFileObject()),
compileOnlyFiles = mutableListOf<JavaFileObject>().also { compileOnlyFiles.forEach { e -> it.add(e.toJavaFileObject()) } }
)
/**
* 编译 [JavaFile] 为 Maven 依赖
* @param pomData Maven POM 实体
* @param outputDirPath 编译输出目录路径
* @param compileOnlyFiles [JavaFile] 仅编译数组 - 默认空
* @throws IllegalStateException 如果编译失败
*/
@JvmName("compileWithJavaFile")
internal fun List<JavaFile>.compile(pomData: MavenPomData, outputDirPath: String, compileOnlyFiles: List<JavaFile> = mutableListOf()) =
CodeCompiler.compile(
pomData = pomData,
outputDirPath = outputDirPath,
files = mutableListOf<JavaFileObject>().also { forEach { e -> it.add(e.toJavaFileObject()) } },
compileOnlyFiles = mutableListOf<JavaFileObject>().also { compileOnlyFiles.forEach { e -> it.add(e.toJavaFileObject()) } }
)
/**
* 编译 [JavaFileObject] 为 Maven 依赖
* @param pomData Maven POM 实体
* @param outputDirPath 编译输出目录路径
* @param compileOnlyFiles [JavaFileObject] 仅编译数组 - 默认空
* @throws IllegalStateException 如果编译失败
*/
@JvmName("compileWithJavaFileObject")
internal fun JavaFileObject.compile(pomData: MavenPomData, outputDirPath: String, compileOnlyFiles: List<JavaFileObject> = mutableListOf()) =
CodeCompiler.compile(pomData, outputDirPath, listOf(this), compileOnlyFiles)
/**
* 编译 [JavaFileObject] 为 Maven 依赖
* @param pomData Maven POM 实体
* @param outputDirPath 编译输出目录路径
* @param compileOnlyFiles [JavaFileObject] 仅编译数组 - 默认空
* @throws IllegalStateException 如果编译失败
*/
@JvmName("compileWithJavaFileObject")
internal fun List<JavaFileObject>.compile(pomData: MavenPomData, outputDirPath: String, compileOnlyFiles: List<JavaFileObject> = mutableListOf()) =
CodeCompiler.compile(pomData, outputDirPath, files = this, compileOnlyFiles)

View File

@@ -0,0 +1,37 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 HighCapable
* https://github.com/HighCapable/SweetProperty
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is Created by fankes on 2023/8/27.
*/
package com.highcapable.sweetproperty.utils.debug
import com.highcapable.sweetproperty.SweetProperty
/**
* 全局异常管理类
*/
internal object SError {
/**
* 抛出异常
* @param msg 消息内容
* @throws IllegalStateException
*/
internal fun make(msg: String): Nothing = error("[${SweetProperty.TAG}] $msg")
}

View File

@@ -0,0 +1,38 @@
/*
* SweetProperty - An easy get project properties anywhere Gradle plugin
* Copyright (C) 2019-2023 HighCapable
* https://github.com/HighCapable/SweetProperty
*
* Apache License Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file is Created by fankes on 2023/8/27.
*/
@file:Suppress("unused")
package com.highcapable.sweetproperty.utils.debug
import com.highcapable.sweetproperty.SweetProperty
/**
* 全局 Log 管理类
*/
internal object SLog {
/**
* 打印 Log
* @param msg 消息内容
*/
internal fun log(msg: String) = println("[${SweetProperty.TAG}] $msg")
}