feat(templates): add API to generate Java source files

This commit is contained in:
Akash Yadav
2023-05-05 21:21:24 +05:30
parent c1e81c3106
commit 59b9913fd9
17 changed files with 674 additions and 318 deletions
Binary file not shown.
+1 -1
View File
@@ -20,5 +20,5 @@ plugins {
}
dependencies {
implementation(projects.subprojects.javaCompiler)
api(projects.subprojects.javaCompiler)
}
+2
View File
@@ -25,6 +25,8 @@ android {
}
dependencies {
api(projects.subprojects.javapoet)
implementation(projects.common)
implementation(projects.logger)
implementation(projects.resources)
@@ -0,0 +1,155 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.templates.base
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.TypeSpec
/**
* Utility for building Java source files.
*
* @author Akash Yadav
*/
class JavaSourceBuilder {
/**
* The type of Java source. A [SourceType] creates a [TypeSpec.Builder] and optionally
* pre-configures it.
*/
interface SourceType {
/**
* Create the [TypeSpec.Builder] for building this type of Java source file.
*/
fun builder(klass: ClassName): TypeSpec.Builder
}
/**
* A class type.
*/
class ClassType : SourceType {
override fun builder(klass: ClassName): TypeSpec.Builder {
return TypeSpec.classBuilder(klass)
}
}
/**
* An interface type.
*/
class InterfaceType : SourceType {
override fun builder(klass: ClassName): TypeSpec.Builder {
return TypeSpec.interfaceBuilder(klass)
}
}
/**
* An enum type.
*/
class EnumType : SourceType {
override fun builder(klass: ClassName): TypeSpec.Builder {
return TypeSpec.enumBuilder(klass)
}
}
/**
* An annotation interface type.
*/
class AnnotationType : SourceType {
override fun builder(klass: ClassName): TypeSpec.Builder {
return TypeSpec.annotationBuilder(klass)
}
}
private val files = hashSetOf<JavaFile>()
/**
* Creates a new Java class.
*
* @param packageName The package name of the class.
* @param className The name of the class.
* @param configure Function to configure the [TypeSpec.Builder].
*/
fun createClass(packageName: String, className: String, configure: TypeSpec.Builder.() -> Unit) {
return createJavaFile(packageName, className, ClassType(), configure)
}
/**
* Creates a new Java enum class.
*
* @param packageName The package name of the class.
* @param className The name of the class.
* @param configure Function to configure the [TypeSpec.Builder].
*/
fun createEnum(packageName: String, className: String, configure: TypeSpec.Builder.() -> Unit) {
return createJavaFile(packageName, className, EnumType(), configure)
}
/**
* Creates a new Java interface.
*
* @param packageName The package name of the interface.
* @param className The name of the interface.
* @param configure Function to configure the [TypeSpec.Builder].
*/
fun createInterface(packageName: String, className: String, configure: TypeSpec.Builder.() -> Unit
) {
return createJavaFile(packageName, className, InterfaceType(), configure)
}
/**
* Creates a new Java annotation interface.
*
* @param packageName The package name of the interface.
* @param className The name of the interface.
* @param configure Function to configure the [TypeSpec.Builder].
*/
fun createAnnotation(packageName: String, className: String,
configure: TypeSpec.Builder.() -> Unit
) {
return createJavaFile(packageName, className, AnnotationType(), configure)
}
/**
* Creates a new Java class.
*
* @param packageName The package name of the class.
* @param className The name of the class.
* @param type The type of the class.
* @param configure Function to configure the [TypeSpec.Builder].
*/
fun createJavaFile(packageName: String, className: String, type: SourceType,
configure: TypeSpec.Builder.() -> Unit
) {
val klass = ClassName.get(packageName, className)
val builder = type.builder(klass).apply(configure)
val file = JavaFile.builder(packageName, builder.build())
file.skipJavaLangImports(true)
files.add(file.build())
}
internal fun ModuleTemplateBuilder.write() {
for (file in files) {
file.writeTo(mainJavaSrc())
}
}
}
@@ -0,0 +1,167 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.templates.base
import com.itsaky.androidide.templates.ModuleTemplate
import com.itsaky.androidide.templates.ModuleTemplateData
import com.itsaky.androidide.templates.RecipeExecutor
import com.itsaky.androidide.templates.SrcSet
import com.itsaky.androidide.templates.TemplateBuilder
import com.itsaky.androidide.templates.TemplateRecipe
import java.io.File
/**
* Abstract [TemplateBuilder] for building module projects.
*
* @property name The name of the module (gradle format, e.g. ':app').
* @author Akash Yadav
*/
abstract class ModuleTemplateBuilder :
ExecutorDataTemplateBuilder<ModuleTemplate, ModuleTemplateData>() {
protected val dependencies = hashSetOf<Dependency>()
protected val javaSourceBuilder = JavaSourceBuilder()
internal var _name: String? = null
val name: String
get() = checkNotNull(_name) { "Name not set to module template" }
protected open fun RecipeExecutor.preConfig() {}
protected open fun RecipeExecutor.postConfig() {}
/**
* Get the asset path for base module project template.
*
* @param path The path for the asset.
* @see com.itsaky.androidide.templates.base.baseAsset
*/
fun baseAsset(path: String) = baseAsset("module", path)
/**
* Get the `build.gradle[.kts]` file for this module.l
*/
fun buildGradleFile(): File {
return data.buildGradleFile()
}
/**
* Get the `java` sources directory for the [SrcSet.Main] source set.
*/
fun mainJavaSrc(): File {
return javaSrc(SrcSet.Main)
}
/**
* Get the `resources` directory for the [SrcSet.Main] source set.
*/
fun mainResourcesDir(): File {
return javaSrc(SrcSet.Main)
}
/**
* Get the `resources` directory for the given [source set][srcSet].
*/
fun resourcesDir(srcSet: SrcSet): File {
return File(srcFolder(srcSet), "resources").also { it.mkdirs() }
}
/**
* Get the `java` source directory for the given [srcSet].
*/
fun javaSrc(srcSet: SrcSet): File {
return File(srcFolder(srcSet), "java").also { it.mkdirs() }
}
/**
* Get the source directory for the given [srcSet].
*
* @param srcSet The type of source directory.
*/
fun srcFolder(srcSet: SrcSet): File {
return data.srcFolder(srcSet)
}
/**
* Configure the Java source files for this module.
*
* @param configure Function for configuring the Java source files.
*/
fun java(configure: JavaSourceBuilder.() -> Unit) {
javaSourceBuilder.apply(configure)
}
/**
* Common pre-recipe configuration.
*
* @param moduleData Called after the base configuration is setup and before the [recipe] is executed. Caller can perform its own
* pre-recipe configuration here. Returns the [ModuleTemplateData] instance.
*/
fun commonPreRecipe(extraConfig: ModuleTemplateConfigurator = {},
moduleData: RecipeExecutor.() -> ModuleTemplateData
): TemplateRecipe = {
val data = moduleData()
this@ModuleTemplateBuilder._data = data
this@ModuleTemplateBuilder._name = data.name
this@ModuleTemplateBuilder._executor = this
data.projectDir.mkdirs()
// Create the main source directory
srcFolder(SrcSet.Main)
preConfig()
extraConfig()
}
/**
* Common post-recipe configuration.
*
* @param extraConfig Called after the [recipe] is executed. Caller can perform its own
* post-recipe configuration here.
*/
fun commonPostRecipe(extraConfig: ModuleTemplateConfigurator = {}): TemplateRecipe = {
// Write the java source files
javaSourceBuilder.apply {
write()
}
// Write build.gradle[.kts]
buildGradle()
postConfig()
extraConfig()
}
/**
* Adds the given dependency to the `build.gradle[.kts]` file for this module.
*/
fun addDependency(dependency: Dependency) {
this.dependencies.add(dependency)
}
/**
* Writes the `build.gradle[.kts]` file for this module.
*/
abstract fun RecipeExecutor.buildGradle()
override fun buildInternal(): ModuleTemplate {
return ModuleTemplate(name, templateName!!, thumb!!, widgets!!, recipe!!)
}
}
@@ -0,0 +1,156 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.templates.base
import com.itsaky.androidide.templates.ModuleTemplate
import com.itsaky.androidide.templates.ModuleTemplateData
import com.itsaky.androidide.templates.ProjectTemplate
import com.itsaky.androidide.templates.ProjectTemplateData
import com.itsaky.androidide.templates.base.root.buildGradleSrcGroovy
import com.itsaky.androidide.templates.base.root.buildGradleSrcKts
import com.itsaky.androidide.templates.base.root.gradleWrapperJar
import com.itsaky.androidide.templates.base.root.gradleWrapperProps
import com.itsaky.androidide.templates.base.root.settingsGradleSrcGroovy
import com.itsaky.androidide.templates.base.root.settingsGradleSrcKts
import java.io.File
/**
* Builder for building project templates.
*
* @author Akash Yadav
*/
class ProjectTemplateBuilder : ExecutorDataTemplateBuilder<ProjectTemplate, ProjectTemplateData>() {
private var _defModule: ModuleTemplateData? = null
private val defModuleTemplate: ModuleTemplate? = null
internal val modules = mutableListOf<ModuleTemplate>()
private val defModule: ModuleTemplateData
get() = checkNotNull(_defModule) { "Module template data not set" }
/**
* Set the template data that will be used to create the default application module in the project.
*
* @param data The module template data to use.
*/
fun setDefaultModuleData(data: ModuleTemplateData) {
_defModule = data
}
/**
* Get the asset path for base root project template.
*
* @param path The path to the asset.
* @see com.itsaky.androidide.templates.base.baseAsset
*/
fun baseAsset(path: String) = baseAsset("root", path)
/**
* Get the `build.gradle[.kts] file for the project.
*/
fun buildGradleFile(): File {
return data.buildGradleFile()
}
/**
* Writes the `build.gradle[.kts]` file in the project root directory.
*/
fun buildGradle() {
executor.save(buildGradleSrc(), buildGradleFile())
}
/**
* Get the source for `build.gradle[.kts]` files.
*/
fun buildGradleSrc(): String {
return if (data.useKts) buildGradleSrcKts() else buildGradleSrcGroovy()
}
/**
* Writes the `settings.gradle[.kts]` file in the project root directory.
*/
fun settingsGradle() {
executor.save(settingsGradleSrc(), settingsGradleFile())
}
/**
* Get the `settings.gradle[.kts]` file for this project.
*/
fun settingsGradleFile(): File {
return File(data.projectDir, data.optonallyKts("settings.gradle"))
}
/**
* Get the source for `settings.gradle[.kts]`.
*/
fun settingsGradleSrc(): String {
return if (data.useKts) settingsGradleSrcKts() else return settingsGradleSrcGroovy()
}
/**
* Writes the `gradle.properties` file in the root project.
*/
fun gradleProps() {
val name = "gradle.properties"
val gradleProps = File(data.projectDir, name)
executor.copyAsset(baseAsset(name), gradleProps)
}
/**
* Writes/copies the Gradle Wrapper related files in the project directory.
*/
fun gradleWrapper() {
val gradlew = File(data.projectDir, "gradlew")
val gradlewBat = File(data.projectDir, "${gradlew.name}.bat")
executor.copyAsset(baseAsset(gradlew.name), gradlew)
executor.copyAsset(baseAsset(gradlewBat.name), gradlewBat)
gradleWrapperJar()
gradleWrapperProps()
}
/**
* Configure the default template for the project.
*
* @param name The name of the module (gradle format, e.g. ':app').
* @param block The module configurator.
*/
fun defaultModule(name: String = ":app", block: AndroidModuleTemplateConfigurator) {
check(defModuleTemplate == null) { "Default module has been already configured" }
val module = AndroidModuleTemplateBuilder().apply {
_name = name
templateName = 0
thumb = 0
preRecipe = commonPreRecipe {
return@commonPreRecipe defModule
}
postRecipe = commonPostRecipe()
block()
}.build()
modules.add(module)
}
override fun buildInternal(): ProjectTemplate {
return ProjectTemplate(modules, templateName!!, thumb!!, widgets!!, recipe!!)
}
}
@@ -20,9 +20,9 @@ package com.itsaky.androidide.templates.base
import com.android.SdkConstants.ANDROID_MANIFEST_XML
import com.itsaky.androidide.templates.ModuleType.AndroidLibrary
import com.itsaky.androidide.templates.RecipeExecutor
import com.itsaky.androidide.templates.SrcSet
import com.itsaky.androidide.templates.base.modules.android.AndroidManifestBuilder
import com.itsaky.androidide.templates.base.modules.android.buildGradleSrc
import com.itsaky.androidide.templates.base.modules.buildGradleFile
import java.io.File
class AndroidModuleTemplateBuilder : ModuleTemplateBuilder() {
@@ -33,7 +33,7 @@ class AndroidModuleTemplateBuilder : ModuleTemplateBuilder() {
* Return the file path to `AndroidManifest.xml`.
*/
fun manifestFile(): File {
return File(data.projectDir, "src/main/$ANDROID_MANIFEST_XML").also { it.parentFile!!.mkdirs() }
return File(srcFolder(SrcSet.Main), ANDROID_MANIFEST_XML).also { it.parentFile!!.mkdirs() }
}
/**
@@ -33,217 +33,15 @@ import com.itsaky.androidide.templates.ProjectTemplate
import com.itsaky.androidide.templates.ProjectTemplateData
import com.itsaky.androidide.templates.ProjectVersionData
import com.itsaky.androidide.templates.R
import com.itsaky.androidide.templates.RecipeExecutor
import com.itsaky.androidide.templates.Sdk
import com.itsaky.androidide.templates.SpinnerWidget
import com.itsaky.androidide.templates.SrcFolder
import com.itsaky.androidide.templates.Template
import com.itsaky.androidide.templates.TemplateBuilder
import com.itsaky.androidide.templates.TemplateData
import com.itsaky.androidide.templates.TemplateRecipe
import com.itsaky.androidide.templates.TextFieldWidget
import com.itsaky.androidide.templates.base.root.buildGradleSrc
import com.itsaky.androidide.templates.base.root.gradleWrapper
import com.itsaky.androidide.templates.base.root.settingsGradleFile
import com.itsaky.androidide.templates.base.root.settingsGradleSrc
import com.itsaky.androidide.templates.enumParameter
import com.itsaky.androidide.templates.stringParameter
import com.itsaky.androidide.utils.AndroidUtils
import com.itsaky.androidide.utils.Environment
import java.io.File
sealed class PrePostRecipeTemplateBuilder<T : Template> : TemplateBuilder<T>() {
internal var preRecipe: TemplateRecipe = {}
internal var postRecipe: TemplateRecipe = {}
override var recipe: TemplateRecipe? = null
get() = {
preRecipe()
field?.let { it() }
postRecipe()
}
}
/**
* @property executor The [RecipeExecutor] instance.
* @property data The project template data.
*/
sealed class ExecutorDataTemplateBuilder<T : Template, D : TemplateData> :
PrePostRecipeTemplateBuilder<T>() {
internal var _executor: RecipeExecutor? = null
internal var _data: D? = null
val executor: RecipeExecutor
get() = checkNotNull(_executor)
val data: D
get() = checkNotNull(_data)
}
/**
* Builder for building project templates.
*/
class ProjectTemplateBuilder : ExecutorDataTemplateBuilder<ProjectTemplate, ProjectTemplateData>() {
private var _defModule: ModuleTemplateData? = null
private val defModuleTemplate: ModuleTemplate? = null
internal val modules = mutableListOf<ModuleTemplate>()
private val defModule: ModuleTemplateData
get() = checkNotNull(_defModule) { "Module template data not set" }
/**
* Set the template data that will be used to create the default application module in the project.
*
* @param data The module template data to use.
*/
fun setDefaultModuleData(data: ModuleTemplateData) {
_defModule = data
}
/**
* Writes the `settings.gradle[.kts]` file in the project root directory.
*/
fun settingsGradle() {
executor.save(settingsGradleSrc(), settingsGradleFile())
}
/**
* Writes the `build.gradle[.kts]` file in the project root directory.
*/
fun buildGradle() {
executor.save(buildGradleSrc(), buildGradleFile())
}
/**
* Get the `build.gradle[.kts] file for the project.l
*/
fun buildGradleFile(): File {
return data.buildGradleFile()
}
/**
* Writes the `gradle.properties` file in the root project.
*/
fun gradleProps() {
val name = "gradle.properties"
val gradleProps = File(data.projectDir, name)
executor.copyAsset(baseAsset(name), gradleProps)
}
/**
* Configure the default template for the project.
*
* @param name The name of the module (gradle format, e.g. ':app').
* @param block The module configurator.
*/
fun defaultModule(name: String = ":app", block: AndroidModuleTemplateConfigurator) {
check(defModuleTemplate == null) { "Default module has been already configured" }
val module = AndroidModuleTemplateBuilder().apply {
_name = name
templateName = 0
thumb = 0
preRecipe = commonPreRecipe {
return@commonPreRecipe defModule
}
postRecipe = commonPostRecipe()
block()
}.build()
modules.add(module)
}
override fun buildInternal(): ProjectTemplate {
return ProjectTemplate(modules, templateName!!, thumb!!, widgets!!, recipe!!)
}
}
/**
* Abstract [TemplateBuilder] for building module projects.
*
* @property name The name of the module (gradle format, e.g. ':app').
*/
abstract class ModuleTemplateBuilder :
ExecutorDataTemplateBuilder<ModuleTemplate, ModuleTemplateData>() {
protected val dependencies = hashSetOf<Dependency>()
internal var _name: String? = null
val name: String
get() = checkNotNull(_name) { "Name not set to module template" }
protected open fun RecipeExecutor.preConfig() {}
protected open fun RecipeExecutor.postConfig() {}
/**
* Creates the source directory for the given [type].
*
* @param type The type of the source directory.
* @param block Function to configure the source folder.
*/
fun srcFolder(type: SrcFolder, block: (File) -> Unit = {}) {
data.srcFolder(type).apply(block)
}
/**
* Common pre-recipe configuration.
*
* @param moduleData Called after the base configuration is setup and before the [recipe] is executed. Caller can perform its own
* pre-recipe configuration here. Returns the [ModuleTemplateData] instance.
*/
fun commonPreRecipe(extraConfig: ModuleTemplateConfigurator = {},
moduleData: RecipeExecutor.() -> ModuleTemplateData
): TemplateRecipe = {
val data = moduleData()
this@ModuleTemplateBuilder._data = data
this@ModuleTemplateBuilder._name = data.name
this@ModuleTemplateBuilder._executor = this
data.projectDir.mkdirs()
// Create the main source directory
srcFolder(SrcFolder.Main)
preConfig()
extraConfig()
}
/**
* Common post-recipe configuration.
*
* @param extraConfig Called after the [recipe] is executed. Caller can perform its own
* post-recipe configuration here.
*/
fun commonPostRecipe(extraConfig: ModuleTemplateConfigurator = {}): TemplateRecipe = {
// Write build.gradle[.kts]
buildGradle()
postConfig()
extraConfig()
}
fun addDependency(dependency: Dependency) {
this.dependencies.add(dependency)
}
/**
* Writes the `build.gradle[.kts]` file for this module.
*/
abstract fun RecipeExecutor.buildGradle()
override fun buildInternal(): ModuleTemplate {
return ModuleTemplate(name, templateName!!, thumb!!, widgets!!, recipe!!)
}
}
/**
* Setup base files for project templates.
@@ -380,40 +178,4 @@ fun baseAndroidModule(block: AndroidModuleTemplateConfigurator): ModuleTemplate
block()
}.build()
}
internal fun ProjectTemplateData.moduleNameToDir(name: String): File {
return File(this.projectDir, moduleNameToDirName(name).replace(':', '/').trim { it == '/' })
}
internal fun moduleNameToDirName(name: String): String {
val result = StringBuilder()
var prev: Char? = null
for (i in name.indices) {
var c = name[i]
if (c == ' ') {
c = '-'
}
if (
// the first character must be a letter
(i == 0 && !c.isLetter())
// chars at other indices must be a letter or digit or
|| !(c.isDigit() || c.isLetter() || c == '-')
// must not include consecutive '-'
|| (prev == '-' && c == '-')
) {
prev = c
continue
}
result.append(c)
prev = c
}
return result.trim { it == ' ' || it == '-' }.toString()
}
@@ -18,24 +18,13 @@
package com.itsaky.androidide.templates.base
import com.itsaky.androidide.templates.BaseTemplateData
import com.itsaky.androidide.templates.ProjectTemplateData
import java.io.File
typealias ProjectTemplateConfigurator = ProjectTemplateBuilder.() -> Unit
typealias ModuleTemplateConfigurator = ModuleTemplateBuilder.() -> Unit
typealias AndroidModuleTemplateConfigurator = AndroidModuleTemplateBuilder.() -> Unit
/**
* Get the asset path for base root project template.
*/
@Suppress("UnusedReceiverParameter")
internal fun ProjectTemplateBuilder.baseAsset(path: String) = baseAsset("root", path)
/**
* Get the asset path for base module project template.
*/
@Suppress("UnusedReceiverParameter")
internal fun ModuleTemplateBuilder.baseAsset(path: String) = baseAsset("module", path)
/**
* Get the asset path for base template.
*/
@@ -45,4 +34,40 @@ internal fun baseAsset(type: String, path: String): String {
internal fun BaseTemplateData.optonallyKts(file: String): String {
return if (useKts) "${file}.kts" else file
}
internal fun ProjectTemplateData.moduleNameToDir(name: String): File {
return File(this.projectDir, moduleNameToDirName(name).replace(':', '/').trim { it == '/' })
}
internal fun moduleNameToDirName(name: String): String {
val result = StringBuilder()
var prev: Char? = null
for (i in name.indices) {
var c = name[i]
if (c == ' ') {
c = '-'
}
if (
// the first character must be a letter
(i == 0 && !c.isLetter())
// chars at other indices must be a letter or digit or
|| !(c.isDigit() || c.isLetter() || c == '-')
// must not include consecutive '-'
|| (prev == '-' && c == '-')
) {
prev = c
continue
}
result.append(c)
prev = c
}
return result.trim { it == ' ' || it == '-' }.toString()
}
@@ -1,26 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.templates.base.modules
import com.itsaky.androidide.templates.base.ModuleTemplateBuilder
import com.itsaky.androidide.templates.base.buildGradleFile
import java.io.File
internal fun ModuleTemplateBuilder.buildGradleFile(): File {
return data.buildGradleFile()
}
@@ -0,0 +1,71 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.templates.base.modules
import com.squareup.javapoet.FieldSpec
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.TypeName
import com.squareup.javapoet.TypeSpec
import jdkx.lang.model.element.Modifier
/**
* Creates a new [FieldSpec] for a static constant and adds it to this [TypeSpec].
*
* @param name The name of the field.
* @param configure A function to configure the field.
*/
fun TypeSpec.Builder.createConstant(type: TypeName, name: String, isPublic: Boolean,
configure: FieldSpec.Builder.() -> Unit
) {
val accessMod = if (isPublic) Modifier.PUBLIC else Modifier.PRIVATE
FieldSpec.builder(type, name, accessMod, Modifier.STATIC, Modifier.FINAL)
.apply(configure)
.build()
.also { addField(it) }
}
/**
* Creates a new [FieldSpec] and adds it to this [TypeSpec].
*
* @param name The name of the field.
* @param configure A function to configure the field.
*/
fun TypeSpec.Builder.createField(type: TypeName, name: String, vararg modifiers: Modifier,
configure: FieldSpec.Builder.() -> Unit
) {
FieldSpec.builder(type, name, *modifiers).apply(configure).build().also { addField(it) }
}
/**
* Creates a new [MethodSpec] for a constructor and adds it to this [TypeSpec].
*
* @param configure A function to configure the method.
*/
fun TypeSpec.Builder.createConstructor(configure: MethodSpec.Builder.() -> Unit) {
MethodSpec.constructorBuilder().apply(configure).build().also { addMethod(it) }
}
/**
* Creates a new [MethodSpec] and adds it to this [TypeSpec].
*
* @param name The name of the method.
* @param configure A function to configure the method.
*/
fun TypeSpec.Builder.createMethod(name: String, configure: MethodSpec.Builder.() -> Unit) {
MethodSpec.methodBuilder(name).apply(configure).build().also { addMethod(it) }
}
@@ -18,16 +18,9 @@
package com.itsaky.androidide.templates.base.root
import com.itsaky.androidide.templates.Language
import com.itsaky.androidide.templates.base.ModuleTemplateBuilder
import com.itsaky.androidide.templates.base.ProjectTemplateBuilder
import com.itsaky.androidide.templates.base.buildGradleFile
import java.io.File
fun ProjectTemplateBuilder.buildGradleSrc(): String {
return if (data.useKts) buildGradleSrcKts() else buildGradleSrcGroovy()
}
private fun ProjectTemplateBuilder.buildGradleSrcKts(): String {
internal fun ProjectTemplateBuilder.buildGradleSrcKts(): String {
return """
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
@@ -42,7 +35,7 @@ private fun ProjectTemplateBuilder.buildGradleSrcKts(): String {
""".trimIndent()
}
private fun ProjectTemplateBuilder.buildGradleSrcGroovy(): String {
internal fun ProjectTemplateBuilder.buildGradleSrcGroovy(): String {
return """
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
@@ -21,21 +21,7 @@ import com.itsaky.androidide.templates.base.ProjectTemplateBuilder
import com.itsaky.androidide.templates.base.baseAsset
import java.io.File
/**
* Writes/copies the Gradle Wrapper related files in the project directory.
*
* @param data The projecte template data.
*/
fun ProjectTemplateBuilder.gradleWrapper() {
val gradlew = File(data.projectDir, "gradlew")
val gradlewBat = File(data.projectDir, "${gradlew.name}.bat")
executor.copyAsset(baseAsset(gradlew.name), gradlew)
executor.copyAsset(baseAsset(gradlewBat.name), gradlewBat)
gradleWrapperJar()
gradleWrapperProps()
}
fun ProjectTemplateBuilder.gradleWrapperJar() {
internal fun ProjectTemplateBuilder.gradleWrapperJar() {
val name = "gradle-wrapper.jar"
val wrapperJar = File(data.projectDir, "gradle/wrapper/${name}")
wrapperJar.parentFile!!.mkdirs()
@@ -43,7 +29,7 @@ fun ProjectTemplateBuilder.gradleWrapperJar() {
executor.copyAsset(baseAsset(name), wrapperJar)
}
fun ProjectTemplateBuilder.gradleWrapperProps() {
internal fun ProjectTemplateBuilder.gradleWrapperProps() {
val name = "gradle-wrapper.properties"
val wrapperProps = File(data.projectDir, "gradle/wrapper/${name}")
wrapperProps.parentFile!!.mkdirs()
@@ -51,7 +37,7 @@ fun ProjectTemplateBuilder.gradleWrapperProps() {
executor.save(gradleWrapperPropsSrc(), wrapperProps)
}
private fun ProjectTemplateBuilder.gradleWrapperPropsSrc(): String {
internal fun ProjectTemplateBuilder.gradleWrapperPropsSrc(): String {
return """
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
@@ -21,14 +21,6 @@ import com.itsaky.androidide.templates.base.ProjectTemplateBuilder
import com.itsaky.androidide.templates.base.optonallyKts
import java.io.File
fun ProjectTemplateBuilder.settingsGradleFile(): File {
return File(data.projectDir, data.optonallyKts("settings.gradle"))
}
fun ProjectTemplateBuilder.settingsGradleSrc(): String {
return if (data.useKts) settingsGradleSrcKts() else return settingsGradleSrcGroovy()
}
internal fun ProjectTemplateBuilder.settingsGradleSrcKts(): String {
return """
pluginManagement {
@@ -0,0 +1,54 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.templates.base
import com.itsaky.androidide.templates.RecipeExecutor
import com.itsaky.androidide.templates.Template
import com.itsaky.androidide.templates.TemplateBuilder
import com.itsaky.androidide.templates.TemplateData
import com.itsaky.androidide.templates.TemplateRecipe
sealed class PrePostRecipeTemplateBuilder<T : Template> : TemplateBuilder<T>() {
internal var preRecipe: TemplateRecipe = {}
internal var postRecipe: TemplateRecipe = {}
override var recipe: TemplateRecipe? = null
get() = {
preRecipe()
field?.let { it() }
postRecipe()
}
}
/**
* @property executor The [RecipeExecutor] instance.
* @property data The project template data.
*/
sealed class ExecutorDataTemplateBuilder<T : Template, D : TemplateData> :
PrePostRecipeTemplateBuilder<T>() {
internal var _executor: RecipeExecutor? = null
internal var _data: D? = null
val executor: RecipeExecutor
get() = checkNotNull(_executor)
val data: D
get() = checkNotNull(_data)
}
@@ -76,7 +76,7 @@ enum class ModuleType(val typeName: String) {
*
* @property folder The name of the folder.
*/
enum class SrcFolder(val folder: String) {
enum class SrcSet(val folder: String) {
/**
* `src/main`.
@@ -153,10 +153,10 @@ class ModuleTemplateData(name: String, val packageName: String, projectDir: File
minSdk: Sdk, val versions: ModuleVersionData = ModuleVersionData(minSdk)
) : BaseTemplateData(name, projectDir, language, useKts) {
private val srcDirs = mutableMapOf<SrcFolder, File>()
private val srcDirs = mutableMapOf<SrcSet, File>()
fun srcFolder(type: SrcFolder): File {
return srcDirs.computeIfAbsent(type) { File(projectDir, "src/${it.folder}") }
fun srcFolder(srcSet: SrcSet): File {
return srcDirs.computeIfAbsent(srcSet) { File(projectDir, "src/${it.folder}") }
.also { it.mkdirs() }
}
}
@@ -19,7 +19,13 @@ package com.itsaky.androidide.templates
import com.google.common.truth.Truth.assertThat
import com.itsaky.androidide.templates.base.modules.android.ManifestActivity
import com.itsaky.androidide.templates.base.modules.createMethod
import com.itsaky.androidide.xml.permissions.Permission
import com.squareup.javapoet.ArrayTypeName
import com.squareup.javapoet.ParameterSpec
import com.squareup.javapoet.TypeName
import jdkx.lang.model.element.Modifier.PUBLIC
import jdkx.lang.model.element.Modifier.STATIC
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@@ -86,8 +92,21 @@ class TemplateBuilderTest {
defaultModule {
manifest {
addPermission(Permission.INTERNET)
addActivity(
ManifestActivity(name = ".MainActivity", isExported = true, isLauncher = true))
addActivity(ManifestActivity(name = ".MainActivity", isExported = true, isLauncher = true))
java {
createClass("com.itsaky", "TestClass") {
createMethod("main") {
addModifiers(PUBLIC, STATIC)
returns(TypeName.VOID)
addParameter(
ParameterSpec.builder(ArrayTypeName.get(java.lang.String::class.java), "args")
.build())
addStatement("System.out.println(\"Hello world!\")")
}
}
}
}
}
}