From a476aa08e74f6f7f3b2c85db44277b9ff80700de Mon Sep 17 00:00:00 2001 From: Madray Haven Date: Mon, 14 Nov 2022 16:45:45 +0800 Subject: [PATCH] feature: support minify enabled --- .run/publishExspToMaven.run.xml | 23 ++++++ README.md | 36 ++++++--- build.gradle.kts | 9 ++- .../sgpublic/exsp/core/ConverterCompiler.kt | 61 +++++++-------- .../sgpublic/exsp/core/PreferenceCompiler.kt | 74 +++++++++++++++---- .../io/github/sgpublic/exsp/util/_Types.kt | 1 - demo/build.gradle.kts | 11 +-- demo/proguard-rules.pro | 4 +- demo/src/main/res/values/strings.xml | 2 +- gradle.properties | 2 +- .../io/github/sgpublic/exsp/ExPreference.kt | 5 +- 11 files changed, 159 insertions(+), 69 deletions(-) create mode 100644 .run/publishExspToMaven.run.xml diff --git a/.run/publishExspToMaven.run.xml b/.run/publishExspToMaven.run.xml new file mode 100644 index 0000000..48b76f7 --- /dev/null +++ b/.run/publishExspToMaven.run.xml @@ -0,0 +1,23 @@ + + + + + + + true + true + false + + + \ No newline at end of file diff --git a/README.md b/README.md index 65e12b1..11794aa 100644 --- a/README.md +++ b/README.md @@ -29,15 +29,15 @@ This is a wrapper library for `SharedPreferences` for Android, based on Lombok ( id 'io.freefair.lombok' version '5.3.0' id 'kotlin-kapt' } - + kapt { keepJavacAnnotationProcessors = true } - + dependencies { implementation "io.github.sgpublic:exsp-runtime:$latest" kapt "io.github.sgpublic:exsp-compiler:$latest" - + def lombok_ver = "1.18.24" compileOnly "org.projectlombok:lombok:$lombok_ver" annotationProcessor "org.projectlombok:lombok:$lombok_ver" @@ -63,18 +63,25 @@ This is a wrapper library for `SharedPreferences` for Android, based on Lombok ( public class TestPreference { @ExValue(defVal = "test") private String testString; - + @ExValue(defVal = "0") private float testFloat; - + @ExValue(defVal = "0") private int testInt; - + @ExValue(defVal = "0") private long testLong; - + @ExValue(defVal = "false") private boolean testBool; + + @ExValue(defVal = "TYPE_A") + private Type testEnum; + + public enum Type { + TYPE_A, TYPE_B; + } } ``` @@ -84,7 +91,7 @@ This is a wrapper library for `SharedPreferences` for Android, based on Lombok ( class App: Application() { override fun onCreate() { super.onCreate() - + ExPreference.init(this) } } @@ -108,13 +115,23 @@ This is a wrapper library for `SharedPreferences` for Android, based on Lombok ( Log.d("TestPreference#testString", test.getTestString()); ``` +7. If you set `isMinifyEnabled = true` in your project, you should add to `proguard-rules.pro`: + + ``` + -keepclassmembers class io.github.sgpublic.exsp.ExPrefs { public static *** get(***); } + ``` + + + ## Custom Type `ExSharedPreference` allows you to save custom types into SharedPreferences, but since SharedPreferences only supports a limited number of types, we use the conversion mechanism to complete this function. +**PS: We've added special support for enum types, so you needn't to add converters for enum types.** + 1. Add the required custom types to the class directly. - **PS: The `defVal` needs to fill in the string of the original type value, not your custom type!** + **Note: The `defVal` needs to fill in the string of the original type value, not your custom type!** ```java @Data @@ -211,4 +228,3 @@ sharedPreference.editor() ### @ExConverter This annotation is used to mark a custom type converter for `ExSharedPreference` processing. - diff --git a/build.gradle.kts b/build.gradle.kts index 355c3ed..d9bf46a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,8 @@ plugins { - id("com.android.library") version "7.3.0" apply false - id("org.jetbrains.kotlin.android") version "1.7.10" apply false - id("com.android.application") version "7.3.0" apply false + val androidVer = "7.3.1" + id("com.android.library") version androidVer apply false + id("com.android.application") version androidVer apply false + + val ktVer = "1.7.21" + id("org.jetbrains.kotlin.android") version ktVer apply false } \ No newline at end of file diff --git a/compiler/src/main/kotlin/io/github/sgpublic/exsp/core/ConverterCompiler.kt b/compiler/src/main/kotlin/io/github/sgpublic/exsp/core/ConverterCompiler.kt index f96b3ec..0c99b54 100644 --- a/compiler/src/main/kotlin/io/github/sgpublic/exsp/core/ConverterCompiler.kt +++ b/compiler/src/main/kotlin/io/github/sgpublic/exsp/core/ConverterCompiler.kt @@ -20,14 +20,14 @@ object ConverterCompiler { .addModifiers(Modifier.PUBLIC) val any = TypeVariableName.get("?") - val Origin = TypeVariableName.get("Origin") - val Target = TypeVariableName.get("Target") + val OriginT = TypeVariableName.get("OriginT") + val TargetT = TypeVariableName.get("TargetT") val anyClass = ParameterizedTypeName.get(ClassName.get(Class::class.java), any) val anyConverter = ParameterizedTypeName.get(ClassName.get(Converter::class.java), any, any) val extendsConverterClass = ParameterizedTypeName.get(ClassName.get(Class::class.java), WildcardTypeName.subtypeOf(ParameterizedTypeName.get(ClassName.get(Converter::class.java), any, any))) - val originClass = ParameterizedTypeName.get(ClassName.get(Class::class.java), Origin) - val knownConverter = ParameterizedTypeName.get(ClassName.get(Converter::class.java), Origin, Target) + val originClass = ParameterizedTypeName.get(ClassName.get(Class::class.java), OriginT) + val knownConverter = ParameterizedTypeName.get(ClassName.get(Converter::class.java), OriginT, TargetT) FieldSpec.builder( ParameterizedTypeName.get(ClassName.get(Map::class.java), anyClass, anyConverter), @@ -43,25 +43,26 @@ object ConverterCompiler { impl.addField(it.build()) } + val originClazzParam = ParameterSpec.builder(originClass, "clazz").build() MethodSpec.methodBuilder("getConverter") .addModifiers(Modifier.PRIVATE, Modifier.STATIC) - .addTypeVariables(listOf(Origin, Target)) - .addParameter(ParameterSpec.builder(originClass, "clazz").build()) - .beginControlFlow("if (!\$T.registry.containsKey(clazz))", ExPreferenceProcessor.ExConverters) - .addStatement("throw new \$T(\"Cannot find converter for \" + clazz + \", " + - "have you created its converter and added @ExConverter?\")", IllegalStateException::class.java) + .addTypeVariables(listOf(OriginT, TargetT)) + .addParameter(originClazzParam) + .beginControlFlow("if (!\$T.registry.containsKey(\$N))", ExPreferenceProcessor.ExConverters, originClazzParam) + .addStatement("throw new \$T(\"Cannot find converter for \" + \$N + \", " + + "have you created its converter and added @ExConverter?\")", IllegalStateException::class.java, originClazzParam) .endControlFlow() - .beginControlFlow("if (!\$T.converters.containsKey(clazz))", ExPreferenceProcessor.ExConverters) + .beginControlFlow("if (!\$T.converters.containsKey(\$N))", ExPreferenceProcessor.ExConverters, originClazzParam) .beginControlFlow("try") - .addStatement("\$T.converters.put(clazz, \$T.registry.get(clazz).newInstance())", - ExPreferenceProcessor.ExConverters, ExPreferenceProcessor.ExConverters) + .addStatement("\$T.converters.put(clazz, \$T.registry.get(\$N).newInstance())", + ExPreferenceProcessor.ExConverters, ExPreferenceProcessor.ExConverters, originClazzParam) .nextControlFlow("catch (IllegalAccessException | InstantiationException e)") - .addStatement("throw new \$T(\"Failed to create instance for \" + \$T.registry.get(clazz) + \"!\")", - RuntimeException::class.java, ExPreferenceProcessor.ExConverters) + .addStatement("throw new \$T(\"Failed to create instance for \" + \$T.registry.get(\$N) + \"!\")", + RuntimeException::class.java, ExPreferenceProcessor.ExConverters, originClazzParam) .endControlFlow() .endControlFlow() - .addStatement("return (\$T<\$T, \$T>) \$T.converters.get(clazz)", - Converter::class.java, Origin, Target, ExPreferenceProcessor.ExConverters) + .addStatement("return (\$T<\$T, \$T>) \$T.converters.get(\$N)", + Converter::class.java, OriginT, TargetT, ExPreferenceProcessor.ExConverters, originClazzParam) .returns(knownConverter) .let { impl.addMethod(it.build()) @@ -69,28 +70,28 @@ object ConverterCompiler { MethodSpec.methodBuilder("toPreference") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) - .addTypeVariables(listOf(Origin, Target)) - .addParameter(ParameterSpec.builder(originClass, "clazz").build()) - .addParameter(ParameterSpec.builder(Origin, "value").build()) - .returns(Target) - .addStatement("\$T<\$T, \$T> converter = \$T.getConverter(clazz)", - Converter::class.java, Origin, Target, ExPreferenceProcessor.ExConverters) + .addTypeVariables(listOf(OriginT, TargetT)) + .addParameter(originClazzParam) + .addParameter(ParameterSpec.builder(OriginT, "value").build()) + .returns(TargetT) + .addStatement("\$T<\$T, \$T> converter = \$T.getConverter(\$N)", + Converter::class.java, OriginT, TargetT, ExPreferenceProcessor.ExConverters, originClazzParam) .addStatement("return converter.toPreference(value)", - Converter::class.java, Origin, Target, ExPreferenceProcessor.ExConverters) + Converter::class.java, OriginT, TargetT, ExPreferenceProcessor.ExConverters) .let { impl.addMethod(it.build()) } MethodSpec.methodBuilder("fromPreference") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) - .addTypeVariables(listOf(Origin, Target)) - .addParameter(ParameterSpec.builder(originClass, "clazz").build()) - .addParameter(ParameterSpec.builder(Target, "value").build()) - .returns(Origin) - .addStatement("\$T<\$T, \$T> converter = \$T.getConverter(clazz)", - Converter::class.java, Origin, Target, ExPreferenceProcessor.ExConverters) + .addTypeVariables(listOf(OriginT, TargetT)) + .addParameter(originClazzParam) + .addParameter(ParameterSpec.builder(TargetT, "value").build()) + .returns(OriginT) + .addStatement("\$T<\$T, \$T> converter = \$T.getConverter(\$N)", + Converter::class.java, OriginT, TargetT, ExPreferenceProcessor.ExConverters, originClazzParam) .addStatement("return converter.fromPreference(value)", - Converter::class.java, Origin, Target, ExPreferenceProcessor.ExConverters) + Converter::class.java, OriginT, TargetT, ExPreferenceProcessor.ExConverters) .let { impl.addMethod(it.build()) } diff --git a/compiler/src/main/kotlin/io/github/sgpublic/exsp/core/PreferenceCompiler.kt b/compiler/src/main/kotlin/io/github/sgpublic/exsp/core/PreferenceCompiler.kt index d15505e..090602a 100644 --- a/compiler/src/main/kotlin/io/github/sgpublic/exsp/core/PreferenceCompiler.kt +++ b/compiler/src/main/kotlin/io/github/sgpublic/exsp/core/PreferenceCompiler.kt @@ -11,24 +11,61 @@ import javax.lang.model.element.Element import javax.lang.model.element.Modifier import javax.lang.model.element.TypeElement import javax.lang.model.element.VariableElement -import javax.tools.Diagnostic object PreferenceCompiler { fun apply(env: RoundEnvironment) { + val any = TypeVariableName.get("?") + val anyClass = ParameterizedTypeName.get(ClassName.get(Class::class.java), any) + val anyLazy = ParameterizedTypeName.get(ClassName.get(Lazy::class.java), any) + + val prefsName = ClassName.get("io.github.sgpublic.exsp", "ExPrefs") + val prefsClazz = TypeSpec.classBuilder(prefsName) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + + val prefs = FieldSpec.builder( + ParameterizedTypeName.get(ClassName.get(Map::class.java), anyClass, anyLazy), + "prefs", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL + ).initializer("new \$T<>()", HashMap::class.java).build() + prefsClazz.addField(prefs) + + val PrefT = TypeVariableName.get("PrefT") + val prefClass = ParameterizedTypeName.get(ClassName.get(Class::class.java), PrefT) + val lazyPref = ParameterizedTypeName.get(ClassName.get(Lazy::class.java), PrefT) + val clazz = ParameterSpec.builder(prefClass, "clazz").build() + MethodSpec.methodBuilder("get") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .addTypeVariable(PrefT) + .addParameter(clazz) + .beginControlFlow("if (\$T.\$N.containsKey(\$N))", prefsName, prefs, clazz) + .addStatement("return (\$T) \$T.\$N.get(\$N)", lazyPref, prefsName, prefs, clazz) + .endControlFlow() + .addStatement("throw new \$T(\"Unknown ExPreference type, did you add @ExPreference?\")", IllegalStateException::class.java) + .returns(lazyPref) + .let { + prefsClazz.addMethod(it.build()) + } + + val static = CodeBlock.builder() + for (element: Element in env.getElementsAnnotatedWith(ExSharedPreference::class.java)) { if (element !is TypeElement) { continue } - applySingle(element) + static.addStatement("\$T.\$N.put(\$L)", prefsName, prefs, applySingle(element)) } + + prefsClazz.addStaticBlock(static.build()) + + JavaFile.builder("io.github.sgpublic.exsp", prefsClazz.build()) + .build().writeTo(ExPreferenceProcessor.mFiler) } - private fun applySingle(element: TypeElement) { + private fun applySingle(element: TypeElement): CodeBlock { val anno = element.getAnnotation(ExSharedPreference::class.java) val originType = ClassName.get(element) val origin = element.simpleName.toString() - val spName = "\"" + anno.name + "\"" + val spName = "\"${anno.name.takeIf { it.isNotBlank() } ?: element.qualifiedName}\"" val pkg = element.qualifiedName.let { val tmp = it.substring(0, it.length - origin.length) @@ -39,9 +76,11 @@ object PreferenceCompiler { } } - val impl = TypeSpec.classBuilder(origin + "_Impl") + val implName = "${origin}_Impl" + val impl = TypeSpec.classBuilder(implName) .superclass(originType) .addModifiers(Modifier.PUBLIC) + val implType = ClassName.get(pkg, implName) MethodSpec.methodBuilder("getSharedPreference") .addModifiers(Modifier.PRIVATE) @@ -72,11 +111,6 @@ object PreferenceCompiler { impl.addMethod(it.build()) } - val save = MethodSpec.methodBuilder("save") - .addModifiers(Modifier.PUBLIC) - .addParameter(originType, "data") - .returns(TypeName.VOID) - for (field: Element in element.enclosedElements) { val defVal = field.getAnnotation(ExValue::class.java)?.defVal if (field !is VariableElement) { @@ -107,7 +141,6 @@ object PreferenceCompiler { var convertedType = type - ExPreferenceProcessor.mMessager.printMessage(Diagnostic.Kind.WARNING, "field: ${field.asType()}") if (type.supported()) { setter.addStatement("\$T converted = value", type) getter.addStatement("\$T origin", type) @@ -170,15 +203,26 @@ object PreferenceCompiler { } setter.addStatement("editor.apply()") - save.addStatement("${field.setterName()}(data.${field.getterName()}())") - impl.addMethod(getter.build()) impl.addMethod(setter.build()) } - impl.addMethod(save.build()) - JavaFile.builder(pkg, impl.build()) .build().writeTo(ExPreferenceProcessor.mFiler) + + val invoke = MethodSpec.methodBuilder("invoke") + .addAnnotation(Override::class.java) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addStatement("return new \$T()", implType) + .returns(originType) + .build() + val originFunction0 = ParameterizedTypeName.get(ClassName.get(Function0::class.java), originType) + return CodeBlock.of("\$T.class, \$T.lazy(\$L)", + originType, ClassName.get("kotlin", "LazyKt"), + TypeSpec.anonymousClassBuilder("") + .addSuperinterface(originFunction0) + .addMethod(invoke) + .build() + ) } } \ No newline at end of file diff --git a/compiler/src/main/kotlin/io/github/sgpublic/exsp/util/_Types.kt b/compiler/src/main/kotlin/io/github/sgpublic/exsp/util/_Types.kt index 355b9b4..f8f83ea 100644 --- a/compiler/src/main/kotlin/io/github/sgpublic/exsp/util/_Types.kt +++ b/compiler/src/main/kotlin/io/github/sgpublic/exsp/util/_Types.kt @@ -82,7 +82,6 @@ private val enu = ExPreferenceProcessor.getElement("java.lang.Enum") fun VariableElement.isEnum(): Boolean { var asElement = ExPreferenceProcessor.asElement(asType()) ?: return false while (asElement.superclass != null) { - ExPreferenceProcessor.mMessager.printMessage(Diagnostic.Kind.WARNING, "asElement: $asElement") if (asElement == enu) { return true } diff --git a/demo/build.gradle.kts b/demo/build.gradle.kts index 3290318..7057734 100644 --- a/demo/build.gradle.kts +++ b/demo/build.gradle.kts @@ -22,8 +22,9 @@ android { } buildTypes { - release { - isMinifyEnabled = false + all { + isMinifyEnabled = true + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } } compileOptions { @@ -42,11 +43,11 @@ kapt { dependencies { implementation("androidx.core:core-ktx:1.9.0") implementation("androidx.appcompat:appcompat:1.5.1") - implementation("com.google.android.material:material:1.6.1") + implementation("com.google.android.material:material:1.7.0") implementation("androidx.constraintlayout:constraintlayout:2.1.4") testImplementation("junit:junit:4.13.2") - androidTestImplementation("androidx.test.ext:junit:1.1.3") - androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0") + androidTestImplementation("androidx.test.ext:junit:1.1.4") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0") implementation(project(":runtime")) kapt(project(":compiler")) diff --git a/demo/proguard-rules.pro b/demo/proguard-rules.pro index 481bb43..2e0cf80 100644 --- a/demo/proguard-rules.pro +++ b/demo/proguard-rules.pro @@ -18,4 +18,6 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +-keepclassmembers class io.github.sgpublic.exsp.ExPrefs { public static *** get(***); } \ No newline at end of file diff --git a/demo/src/main/res/values/strings.xml b/demo/src/main/res/values/strings.xml index b5a7f60..24e6acc 100644 --- a/demo/src/main/res/values/strings.xml +++ b/demo/src/main/res/values/strings.xml @@ -1,3 +1,3 @@ - demp-kotlin + demo-kotlin \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 8f6337a..284ba63 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,4 +4,4 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 android.useAndroidX=true android.nonTransitiveRClass=true -exsp.version=1.0.0-alpha04 \ No newline at end of file +exsp.version=1.0.0-alpha05 \ No newline at end of file diff --git a/runtime/src/main/java/io/github/sgpublic/exsp/ExPreference.kt b/runtime/src/main/java/io/github/sgpublic/exsp/ExPreference.kt index 906a433..4757f4e 100644 --- a/runtime/src/main/java/io/github/sgpublic/exsp/ExPreference.kt +++ b/runtime/src/main/java/io/github/sgpublic/exsp/ExPreference.kt @@ -23,8 +23,9 @@ object ExPreference { @JvmStatic fun get(clazz: Class): T { - val target = Class.forName(clazz.name + "_Impl") + val target = Class.forName("io.github.sgpublic.exsp.ExPrefs") @Suppress("UNCHECKED_CAST") - return target.newInstance() as T + val result by target.getMethod("get", Class::class.java).invoke(null, clazz) as Lazy + return result } } \ No newline at end of file