Skip to content

Commit

Permalink
Implement dynamic code loading using code generator x reflection
Browse files Browse the repository at this point in the history
  • Loading branch information
wisnukurniawan committed Jan 24, 2020
1 parent 3096f22 commit ccd5642
Show file tree
Hide file tree
Showing 18 changed files with 187 additions and 38 deletions.
6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply from: "$rootDir/build-system/dependencies-feature-generator.gradle"

android {
Expand All @@ -27,6 +28,8 @@ dependencies {
}

implementation project(':launcher:main')
implementation project(':launcher:annotations')
kapt project(':launcher:compiler')

implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.0.2'
Expand Down
21 changes: 11 additions & 10 deletions app/src/main/java/com/wisnu/ordertiket/MainLauncher.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
package com.wisnu.ordertiket

import android.app.Application
import com.wisnu.feature.order.flight.OrderFlightFeatureApplication
import com.wisnu.feature.order.hotel.OrderHotelFeatureApplication
import com.wisnu.feature.order.train.OrderTrainFeatureApplication
import com.wisnu.feature.setting.SettingFeatureApplication
import com.wisnu.feature.transaction.TransactionFeatureApplication
import com.wisnu.launcher.annotations.RegisterFeature
import com.wisnu.launcher.main.BaseLauncher
import com.wisnu.launcher.main.FeatureApplicationClassName

@RegisterFeature(
FeatureApplicationClassName.ORDER_FLIGHT,
FeatureApplicationClassName.ORDER_HOTEL,
FeatureApplicationClassName.ORDER_TRAIN,
FeatureApplicationClassName.TRANSACTION,
FeatureApplicationClassName.SETTING
)
class MainLauncher(application: Application) : BaseLauncher(application) {
init {
registerFeatureApplication(OrderFlightFeatureApplication())
registerFeatureApplication(OrderHotelFeatureApplication())
registerFeatureApplication(OrderTrainFeatureApplication())
registerFeatureApplication(TransactionFeatureApplication())
registerFeatureApplication(SettingFeatureApplication())
MainLauncherUtils.buildFeatures()
.forEach { registerFeatureApplication(it) }
}
}
1 change: 1 addition & 0 deletions build-system/dependencies-module.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply from: "$rootDir/build-system/dependencies-feature-generator.gradle"

androidExtensions {
Expand Down
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ android.useAndroidX=true
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
kapt.incremental.apt=true
8 changes: 6 additions & 2 deletions launcher/annotations/build.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
apply plugin: 'java-library'

sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}

sourceCompatibility = "7"
targetCompatibility = "7"
Empty file.
21 changes: 0 additions & 21 deletions launcher/annotations/proguard-rules.pro

This file was deleted.

2 changes: 0 additions & 2 deletions launcher/annotations/src/main/AndroidManifest.xml

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.wisnu.launcher.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface RegisterFeature {
String[] value();
}
3 changes: 0 additions & 3 deletions launcher/annotations/src/main/res/values/strings.xml

This file was deleted.

1 change: 1 addition & 0 deletions launcher/compiler/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ apply plugin: 'kotlin'
dependencies {
implementation project(':launcher:annotations')
implementation "com.squareup:javapoet:1.11.1"
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

sourceCompatibility = JavaVersion.VERSION_1_8
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.wisnu.launcher.compiler

private val classMap = mutableMapOf<String, Class<*>>()

private inline fun <reified T : Any> Any.castOrNull() = this as? T

internal fun <T> String.loadClassOrNull(): Class<out T>? =
classMap.getOrPut(this) {
try {
Class.forName(this)
} catch (e: ClassNotFoundException) {
return null
}
}.castOrNull()
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.wisnu.launcher.compiler

import com.squareup.javapoet.ClassName
import com.wisnu.launcher.annotations.RegisterFeature
import javax.lang.model.element.TypeElement
import javax.lang.model.util.Elements

class FeatureAnnotatedClass(element: TypeElement, elementUtils: Elements) {

val className: ClassName
val features: Array<String>

init {
val packageName = getPackageName(elementUtils, element)
val originalClassName = getClassName(element, packageName)
this.className = ClassName.get(packageName, originalClassName)
this.features = element.getAnnotation(RegisterFeature::class.java).value
}

private fun getPackageName(elementUtils: Elements, type: TypeElement): String {
return elementUtils.getPackageOf(type).qualifiedName.toString()
}

private fun getClassName(element: TypeElement, packageName: String): String {
return element.qualifiedName.toString().substring(packageName.length + 1).replace('.', '$')
}

override fun toString(): String {
return className.toString()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.wisnu.launcher.compiler

import com.squareup.javapoet.*
import java.io.IOException
import java.util.*
import javax.annotation.processing.Filer
import javax.lang.model.element.Modifier

class FeatureListWriter(private val featureAnnotatedClass: FeatureAnnotatedClass) {

@Throws(IOException::class)
fun write(filer: Filer) {
val classBuilder = TypeSpec.classBuilder("${featureAnnotatedClass.className.simpleName()}Utils")
classBuilder.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
classBuilder.addMethod(createBuildFeaturesMethod())
JavaFile.builder(featureAnnotatedClass.className.packageName(), classBuilder.build()).build().writeTo(filer)
}

private fun createBuildFeaturesMethod(): MethodSpec {
val methodName = "buildFeatures"
val localVariableName = "Features"
val featureType = ClassName.get("com.wisnu.launcher.main", "FeatureApplication")
val listType = ClassName.get(List::class.java)
val arrayListType = ClassName.get(ArrayList::class.java)
val returnType = ParameterizedTypeName.get(listType, featureType)

val buildFeatures = MethodSpec.methodBuilder(methodName)
.addModifiers(Modifier.STATIC)
.addModifiers(Modifier.FINAL)
.returns(returnType)
.addStatement("\$T $localVariableName = new \$T<>()", returnType, arrayListType)

featureAnnotatedClass.features
.filter { it.loadClassOrNull<Any>() != null }
.forEach { buildFeatures.addStatement("$localVariableName.add(new $it())") }

buildFeatures.addStatement("return $localVariableName")

return buildFeatures.build()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.wisnu.launcher.compiler

import com.wisnu.launcher.annotations.RegisterFeature
import java.io.IOException
import javax.annotation.processing.*
import javax.lang.model.SourceVersion
import javax.lang.model.element.TypeElement
import javax.lang.model.util.Elements
import javax.tools.Diagnostic
import kotlin.properties.Delegates
import javax.annotation.processing.ProcessingEnvironment

class FeatureProcessor : AbstractProcessor() {
private var filer: Filer by Delegates.notNull()
private var messager: Messager by Delegates.notNull()
private var elementUtils: Elements by Delegates.notNull()

@Synchronized
override fun init(processingEnv: ProcessingEnvironment) {
super.init(processingEnv)
filer = processingEnv.filer
messager = processingEnv.messager
elementUtils = processingEnv.elementUtils
}

override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latestSupported()

override fun getSupportedAnnotationTypes(): Set<String> = hashSetOf(RegisterFeature::class.java.canonicalName)

override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
parseEnv(roundEnv, elementUtils)
.map { FeatureListWriter(it) }
.forEach {
try {
it.write(filer)
} catch (e: IOException) {
messager.printMessage(Diagnostic.Kind.ERROR, e.message)
}
}
return true
}

private fun parseEnv(roundEnv: RoundEnvironment, elementUtils: Elements): List<FeatureAnnotatedClass> {
return roundEnv.getElementsAnnotatedWith(RegisterFeature::class.java).map {
FeatureAnnotatedClass(it as TypeElement, elementUtils)
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.wisnu.launcher.compiler.FeatureProcessor
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,12 @@ object FeatureType {
const val ORDER_FLIGHT = 2
const val ORDER_TRAIN = 3
const val ORDER_HOTEL = 4
}

object FeatureApplicationClassName {
const val ORDER_FLIGHT = "com.wisnu.feature.order.flight.OrderFlightFeatureApplication"
const val ORDER_HOTEL = "com.wisnu.feature.order.hotel.OrderHotelFeatureApplication"
const val ORDER_TRAIN = "com.wisnu.feature.order.train.OrderTrainFeatureApplication"
const val TRANSACTION = "com.wisnu.feature.transaction.TransactionFeatureApplication"
const val SETTING = "com.wisnu.feature.setting.SettingFeatureApplication"
}

0 comments on commit ccd5642

Please sign in to comment.