Skip to content

Commit

Permalink
Fix - use Types instead of Class
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcinMoskala committed Nov 7, 2017
1 parent a646e68 commit 7ed8816
Show file tree
Hide file tree
Showing 11 changed files with 226 additions and 33 deletions.
@@ -0,0 +1,22 @@
package com.marcinmoskala.kotlinpreferences

import android.support.test.runner.AndroidJUnit4
import com.google.gson.Gson
import com.marcinmoskala.kotlinpreferences.gson.GsonSerializer
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class DeserializationTest : GsonBaseTest() {

data class User(var name: String, var age: Int = 0)

@Test
fun listDeserializationTest() {
val json = "[{\"age\"=\"0\", \"name\"=\"Name0\"}, {\"age\"=\"1\", \"name\"=\"Name1\"}, {\"age\"=\"2\", \"name\"=\"Name2\"}]"
val users = GsonSerializer(Gson()).deserialize(json, object : TypeToken<List<User>>() {}.type)
val expected = List(3) { i -> User("Name$i", i) }
Assert.assertEquals(expected, users)
}
}
Expand Up @@ -14,7 +14,7 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class Issue2Test {

class User(var name: String, var age: Int = 0)
data class User(var name: String, var age: Int = 0)

object AppPreference : PreferenceHolder() {
var users: List<User>? by bindToPreferenceFieldNullable()
Expand All @@ -29,6 +29,7 @@ class Issue2Test {
fun booleanDefaultChangeTest() {
val users = List(6) { i -> User("Name$i", i) }
AppPreference.users = users
AppPreference.clearCache()
val readUsers = AppPreference.users
Assert.assertEquals(users, readUsers)
}
Expand Down
Expand Up @@ -21,4 +21,4 @@ class ObjectsTest: GsonBaseTest() {
val game1 = Game(Character("Marcin", "Human", "Wizzard"), GameMode.Hard, 100)
testValues(ComplexTestPreferences::savedGame, null, game1)
}
}
}
Expand Up @@ -3,17 +3,15 @@ package com.marcinmoskala.kotlinpreferences.gson
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.marcinmoskala.kotlinpreferences.Serializer
import java.lang.reflect.Type

class GsonSerializer(val gson: Gson): Serializer {
class GsonSerializer(val gson: Gson) : Serializer {

override fun serialize(toSerialize: Any?): String? = gson.toJson(toSerialize)

override fun <T> deserialize(serialized: String?): Any? {
val type = object : TypeToken<T>() {}.type
return try {
gson.fromJson<T>(serialized, type)
} catch (e: Throwable) {
throw Error("Error in parsing to $type. The string to parse: \"$this\"", e)
}
override fun deserialize(serialized: String?, type: Type): Any? = try {
gson.fromJson<Any>(serialized, type)
} catch (e: Throwable) {
throw Error("Error in parsing to $type. The string to parse: \"$this\"", e)
}
}
Expand Up @@ -3,44 +3,56 @@ package com.marcinmoskala.kotlinpreferences
import android.content.Context
import android.content.SharedPreferences
import android.preference.PreferenceManager
import com.marcinmoskala.kotlinpreferences.bindings.Clearable
import com.marcinmoskala.kotlinpreferences.bindings.PreferenceFieldBinder
import com.marcinmoskala.kotlinpreferences.bindings.PreferenceFieldBinderNullable
import java.lang.reflect.Type
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.jvm.isAccessible

abstract class PreferenceHolder {

protected inline fun <reified T : Any> bindToPreferenceField(default: T, key: String? = null): ReadWriteProperty<PreferenceHolder, T>
= bindToPreferenceField(T::class, default, key)
= bindToPreferenceField(T::class, object : TypeToken<T>() {}.type, default, key)

protected inline fun <reified T : Any> bindToPreferenceFieldNullable(key: String? = null): ReadWriteProperty<PreferenceHolder, T?>
= bindToPreferenceFieldNullable(T::class, key)
= bindToPreferenceFieldNullable(T::class, object : TypeToken<T>() {}.type, key)

protected fun <T : Any> bindToPreferenceField(clazz: KClass<T>, default: T, key: String?): ReadWriteProperty<PreferenceHolder, T>
= PreferenceFieldBinder(clazz, default, key)
protected fun <T : Any> bindToPreferenceField(clazz: KClass<T>, type: Type, default: T, key: String?): ReadWriteProperty<PreferenceHolder, T>
= PreferenceFieldBinder(clazz, type, default, key)

protected fun <T : Any> bindToPreferenceFieldNullable(clazz: KClass<T>, key: String?): ReadWriteProperty<PreferenceHolder, T?>
= PreferenceFieldBinderNullable(clazz, key)
protected fun <T : Any> bindToPreferenceFieldNullable(clazz: KClass<T>, type: Type, key: String?): ReadWriteProperty<PreferenceHolder, T?>
= PreferenceFieldBinderNullable(clazz, type, key)

/**
* Function used to clear all SharedPreference and PreferenceHolder data. Useful especially
* during tests or when implementing Logout functionality.
*/
fun clear() {
forEachDelegate { delegate, property ->
delegate.clear(this, property)
}
}

fun clearCache() {
forEachDelegate { delegate, property ->
delegate.clearCache()
}
}

private fun forEachDelegate(f: (Clearable, KProperty<*>) -> Unit) {
val pref = preferences ?: return
val properties = this::class.declaredMemberProperties
.filterIsInstance<KProperty1<SharedPreferences, *>>()
for (p in properties) {
val prevAccessible = p.isAccessible
if (!prevAccessible) p.isAccessible = true
val delegate = p.getDelegate(pref)
when (delegate) {
is PreferenceFieldBinder<*> -> delegate.clear(this, p)
is PreferenceFieldBinderNullable<*> -> delegate.clear(this, p)
}
if (delegate is Clearable) f(delegate, p)
p.isAccessible = prevAccessible
}
}
Expand Down
@@ -1,8 +1,10 @@
package com.marcinmoskala.kotlinpreferences

import java.lang.reflect.Type

interface Serializer {

fun serialize(toSerialize: Any?): String?

fun <T> deserialize(serialized: String?): Any?
fun deserialize(serialized: String?, type: Type): Any?
}
@@ -0,0 +1,129 @@
package com.marcinmoskala.kotlinpreferences

import java.io.Serializable
import java.lang.reflect.GenericArrayType
import java.lang.reflect.Modifier
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.lang.reflect.WildcardType
import java.util.Arrays

open class TypeToken<T> protected constructor() {

private val superclass = javaClass.genericSuperclass as ParameterizedType
val type: Type = canonicalize(superclass.actualTypeArguments[0])

private class ParameterizedTypeImpl(ownerType: Type?, rawType: Type, vararg typeArguments: Type) : ParameterizedType, Serializable {
private val ownerType: Type?
private val rawType: Type
private val typeArguments: List<Type>

init {
// require an owner type if the raw type needs it
if (rawType is Class<*>) {
val isStaticOrTopLevelClass = Modifier.isStatic(rawType.modifiers) || rawType.enclosingClass == null
checkArgument(ownerType != null || isStaticOrTopLevelClass)
}

this.ownerType = if (ownerType == null) null else canonicalize(ownerType)
this.rawType = canonicalize(rawType)
this.typeArguments = typeArguments.map { canonicalize(it) }
}

override fun getActualTypeArguments() = typeArguments.toTypedArray()

override fun getRawType() = rawType

override fun getOwnerType() = ownerType

override fun toString(): String {
val stringBuilder = StringBuilder(30 * (typeArguments.size + 1))
stringBuilder.append(typeToString(rawType))

if (typeArguments.isEmpty()) {
return stringBuilder.toString()
}

stringBuilder.append("<").append(typeToString(typeArguments[0]))
for (i in 1 until typeArguments.size) {
stringBuilder.append(", ").append(typeToString(typeArguments[i]))
}
return stringBuilder.append(">").toString()
}
}

private class GenericArrayTypeImpl(componentType: Type) : GenericArrayType, Serializable {

private val componentType: Type = canonicalize(componentType)

override fun getGenericComponentType() = componentType

override fun toString() = typeToString(componentType) + "[]"
}

private class WildcardTypeImpl(upperBounds: Array<Type>, lowerBounds: Array<Type>) : WildcardType, Serializable {
private val upperBound: Type
private val lowerBound: Type?

init {
checkArgument(lowerBounds.size <= 1)
checkArgument(upperBounds.size == 1)

if (lowerBounds.size == 1) {
checkNotNull(lowerBounds[0])
checkNotPrimitive(lowerBounds[0])
checkArgument(upperBounds[0] === Any::class.java)
this.lowerBound = canonicalize(lowerBounds[0])
this.upperBound = Any::class.java

} else {
checkNotNull(upperBounds[0])
checkNotPrimitive(upperBounds[0])
this.lowerBound = null
this.upperBound = canonicalize(upperBounds[0])
}
}

override fun getUpperBounds() = arrayOf(upperBound)

override fun getLowerBounds() =
if (lowerBound != null) arrayOf(lowerBound) else EMPTY_TYPE_ARRAY

override fun toString(): String = when {
lowerBound != null -> "? super " + typeToString(lowerBound)
upperBound === Any::class.java -> "?"
else -> "? extends " + typeToString(upperBound)
}
}

companion object {

internal val EMPTY_TYPE_ARRAY = arrayOf<Type>()

internal fun canonicalize(type: Type): Type = if (type is Class<*>) {
if (type.isArray) GenericArrayTypeImpl(canonicalize(type.componentType)) else type
} else if (type is ParameterizedType) {
ParameterizedTypeImpl(type.ownerType, type.rawType, *type.actualTypeArguments)
} else if (type is GenericArrayType) {
GenericArrayTypeImpl(type.genericComponentType)
} else if (type is WildcardType) {
WildcardTypeImpl(type.upperBounds, type.lowerBounds)
} else {
type
}

internal fun typeToString(type: Type): String {
return if (type is Class<*>) type.name else type.toString()
}

internal fun checkNotPrimitive(type: Type) {
checkArgument(type !is Class<*> || !type.isPrimitive)
}

internal fun <T> checkNotNull(obj: T?): T = if (obj != null) obj else throw NullPointerException()

internal fun checkArgument(condition: Boolean) {
if (!condition) throw IllegalArgumentException()
}
}
}
@@ -0,0 +1,9 @@
package com.marcinmoskala.kotlinpreferences.bindings

import com.marcinmoskala.kotlinpreferences.PreferenceHolder
import kotlin.reflect.KProperty

interface Clearable {
fun clear(thisRef: PreferenceHolder, property: KProperty<*>)
fun clearCache()
}
Expand Up @@ -4,17 +4,27 @@ import android.content.SharedPreferences
import com.marcinmoskala.kotlinpreferences.PreferenceHolder
import com.marcinmoskala.kotlinpreferences.PreferenceHolder.Companion.getPreferencesOrThrowError
import com.marcinmoskala.kotlinpreferences.PreferenceHolder.Companion.testingMode
import java.lang.reflect.Type
import kotlin.concurrent.thread
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KClass
import kotlin.reflect.KProperty

internal class PreferenceFieldBinder<T : Any>(val clazz: KClass<T>, val default: T, val key: String?) : ReadWriteProperty<PreferenceHolder, T> {
internal class PreferenceFieldBinder<T : Any>(
private val clazz: KClass<T>,
private val type: Type,
private val default: T,
private val key: String?
) : ReadWriteProperty<PreferenceHolder, T>, Clearable {

fun clear(thisRef: PreferenceHolder, property: KProperty<*>) {
override fun clear(thisRef: PreferenceHolder, property: KProperty<*>) {
setValue(thisRef, property, default)
}

override fun clearCache() {
field = null
}

var field: T? = null

override operator fun getValue(thisRef: PreferenceHolder, property: KProperty<*>): T = when {
Expand Down Expand Up @@ -42,6 +52,6 @@ internal class PreferenceFieldBinder<T : Any>(val clazz: KClass<T>, val default:

private fun SharedPreferences.getValue(property: KProperty<*>): T {
val key = getKey(key, property)
return getFromPreference(clazz, default, key)
return getFromPreference(clazz, type, default, key)
}
}
Expand Up @@ -3,17 +3,27 @@ package com.marcinmoskala.kotlinpreferences.bindings
import com.marcinmoskala.kotlinpreferences.PreferenceHolder
import com.marcinmoskala.kotlinpreferences.PreferenceHolder.Companion.getPreferencesOrThrowError
import com.marcinmoskala.kotlinpreferences.PreferenceHolder.Companion.testingMode
import java.lang.reflect.Type
import kotlin.concurrent.thread
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KClass
import kotlin.reflect.KProperty

internal class PreferenceFieldBinderNullable<T : Any>(val clazz: KClass<T>, val key: String?) : ReadWriteProperty<PreferenceHolder, T?> {
internal class PreferenceFieldBinderNullable<T : Any>(
private val clazz: KClass<T>,
private val type: Type,
private val key: String?
) : ReadWriteProperty<PreferenceHolder, T?>, Clearable {

fun clear(thisRef: PreferenceHolder, property: KProperty<*>) {
override fun clear(thisRef: PreferenceHolder, property: KProperty<*>) {
setValue(thisRef, property, null)
}

override fun clearCache() {
propertySet = false
field = null
}

var propertySet: Boolean = false
var field: T? = null

Expand Down Expand Up @@ -52,7 +62,7 @@ internal class PreferenceFieldBinderNullable<T : Any>(val clazz: KClass<T>, val
val key = getKey(key, property)
val pref = getPreferencesOrThrowError()
if (!pref.contains(key)) return null
return pref.getFromPreference(clazz, key)
return pref.getFromPreference(clazz, type, key)
}

private fun removeValue(property: KProperty<*>) {
Expand Down

0 comments on commit 7ed8816

Please sign in to comment.