1+ package com.dengzii.plugin.template.tools
2+
3+ import com.dengzii.plugin.template.tools.PersistentConfig.ObjectSerializer
4+ import com.google.gson.Gson
5+ import com.google.gson.JsonParseException
6+ import com.intellij.ide.util.PropertiesComponent
7+ import com.intellij.openapi.project.Project
8+ import kotlin.properties.ReadWriteProperty
9+ import kotlin.reflect.KClass
10+ import kotlin.reflect.KProperty
11+
12+ /* *
13+ * Make data persistence more easy.
14+ *
15+ * Delegate field's getter/setter to [PropertiesComponent], the complex type will serialize/deserialize
16+ * use gson by default, you can custom [ObjectSerializer] instead it.
17+ *
18+ * @param propertiesComp The [PropertiesComponent]
19+ * @param project The project use for obtain [PropertiesComponent], ignored when propertiesComp is non-null.
20+ * @param keySuffix The key suffix use for persist.
21+ * @param objectSerializer The serialize/deserialize factory for complex type.
22+ *
23+ * @author https://github.com/dengzii
24+ */
25+ open class PersistentConfig (
26+ propertiesComp : PropertiesComponent ? = null ,
27+ project : Project ? = null ,
28+ private val keySuffix : String = " KEY" ,
29+ private val objectSerializer : ObjectSerializer = JsonObjectSerializer ()
30+ ) {
31+
32+ private val propertiesComponent: PropertiesComponent = propertiesComp ? : if (project == null ) {
33+ PropertiesComponent .getInstance()
34+ } else {
35+ PropertiesComponent .getInstance(project)
36+ }
37+
38+ /* *
39+ * Return the delegate for field need to persist.
40+ *
41+ * @param defaultValue The default when load value failed.
42+ * @param keyName The key name use for persist.
43+ *
44+ * @return [PropertyDelegate]
45+ */
46+ inline fun <reified T > persistentProperty (defaultValue : T , keyName : String? = null): PropertyDelegate <T > {
47+ return when (defaultValue) {
48+ is Int? ,
49+ is Boolean? ,
50+ is Float? ,
51+ is String? ,
52+ is Array <* >? -> {
53+ PropertyDelegate (defaultValue, T ::class , keyName)
54+ }
55+ else -> PropertyDelegate (defaultValue, T ::class , keyName)
56+ }
57+ }
58+
59+ /* *
60+ * The interface defines how to serializer/deserializer the object not primitive type.
61+ */
62+ interface ObjectSerializer {
63+ fun serialize (obj : Any , clazz : KClass <* >): String
64+ fun deserialize (str : String , clazz : KClass <* >): Any
65+ }
66+
67+ /* *
68+ * The json serializer/deserializer use gson.
69+ */
70+ class JsonObjectSerializer : ObjectSerializer {
71+
72+ private val gson: Gson = Gson ().newBuilder()
73+ .setLenient()
74+ .serializeNulls()
75+ .create()
76+
77+ @Throws(JsonParseException ::class )
78+ override fun serialize (obj : Any , clazz : KClass <* >): String {
79+ return gson.toJson(obj)
80+ }
81+
82+ @Throws(JsonParseException ::class )
83+ override fun deserialize (str : String , clazz : KClass <* >): Any {
84+ return gson.fromJson(str, clazz.java)
85+ }
86+ }
87+
88+ /* *
89+ * This class is a delegate class for a field need to persist.
90+ *
91+ * @param default The default when read property failed, the following situation will return [default]:
92+ * 1, The key does not exist.
93+ * 2, Read value successful but serialize/deserialize failed.
94+ * 3, An exception was caught.
95+ * @param clazz The KClass of field.
96+ * @param keyName The key name, the field name is used when null.
97+ */
98+ inner class PropertyDelegate <T : Any ?>
99+ constructor (
100+ private val default: T ,
101+ private val clazz: KClass <* >,
102+ private val keyName: String? = null
103+ ) : ReadWriteProperty <PersistentConfig , T ?> {
104+
105+ @Suppress(" UNCHECKED_CAST" )
106+ override fun getValue (thisRef : PersistentConfig , property : KProperty <* >): T ? {
107+ val keyName = getKeyName(property)
108+ return try {
109+ with (thisRef.propertiesComponent) {
110+ when (clazz) {
111+ Int ::class -> getInt(keyName, default as Int )
112+ Boolean ::class -> getBoolean(keyName, default as Boolean )
113+ String ::class -> getValue(keyName, default as String )
114+ Float ::class -> getFloat(keyName, default as Float )
115+ Array <String >::class -> getValues(keyName)
116+ // deserialize to object
117+ else -> {
118+ val v = getValue(keyName)
119+ if (v != null ) {
120+ objectSerializer.deserialize(v, clazz)
121+ } else {
122+ default
123+ }
124+ }
125+ }
126+ } as T
127+ } catch (ignore: TypeCastException ) {
128+ default
129+ } catch (e: Throwable ) {
130+ e.printStackTrace()
131+ default
132+ }
133+ }
134+
135+ override fun setValue (thisRef : PersistentConfig , property : KProperty <* >, value : T ? ) {
136+ val keyName = getKeyName(property)
137+ with (thisRef.propertiesComponent) {
138+ when (value) {
139+ is Int -> setValue(keyName, value, default as Int )
140+ is Boolean -> setValue(keyName, value, default as Boolean )
141+ is String -> setValue(keyName, value, default as String )
142+ is Float -> setValue(keyName, value, default as Float )
143+ is Array <* > -> {
144+ val arr = value.filterIsInstance<String >().toTypedArray()
145+ setValues(keyName, arr)
146+ }
147+ null -> unsetValue(keyName)
148+ else -> {
149+ try {
150+ val serialized = objectSerializer.serialize(value, clazz)
151+ setValue(keyName, serialized)
152+ } catch (e: Throwable ) {
153+ throw RuntimeException (" Type unsupported." , e)
154+ }
155+ }
156+ }
157+ }
158+ }
159+
160+ private fun getKeyName (property : KProperty <* >): String {
161+ return " ${keySuffix} _${keyName ? : property.name} "
162+ }
163+ }
164+ }
0 commit comments