Skip to content

Commit

Permalink
feature: support minify enabled
Browse files Browse the repository at this point in the history
  • Loading branch information
sgpublic committed Nov 14, 2022
1 parent 07bbc0d commit a476aa0
Show file tree
Hide file tree
Showing 11 changed files with 159 additions and 69 deletions.
23 changes: 23 additions & 0 deletions .run/publishExspToMaven.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="publishExspToMaven" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="publishExspToMaven" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>
36 changes: 26 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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;
}
}
```

Expand All @@ -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)
}
}
Expand All @@ -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
Expand Down Expand Up @@ -211,4 +228,3 @@ sharedPreference.editor()
### @ExConverter

This annotation is used to mark a custom type converter for `ExSharedPreference` processing.

9 changes: 6 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -43,54 +43,55 @@ 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())
}

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())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
11 changes: 6 additions & 5 deletions demo/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ android {
}

buildTypes {
release {
isMinifyEnabled = false
all {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
Expand All @@ -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"))
Expand Down
Loading

0 comments on commit a476aa0

Please sign in to comment.