-
Notifications
You must be signed in to change notification settings - Fork 10
/
types.kt
271 lines (234 loc) · 11.4 KB
/
types.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
package net.aquadc.persistence.type
import net.aquadc.persistence.struct.FieldDef
import net.aquadc.persistence.struct.FieldSet
import net.aquadc.persistence.struct.Schema
/**
* The root of all types.
* “Ilk” is just another synonym for “type” or “kind”.
*
* Used by :sql to use native types like `uuid`, `point` etc.
*/
interface Ilk<T, out DT : DataType<T>> {
val type: DT
val custom: CustomType<T>?
}
/**
* Describes type of stored values and underlying serialization techniques.
* This property is a part of serialization ABI.
*
* Replacing one DataType<T> with another DataType<T> (which is OK for source and binary compatibility)
* may break serialization compatibility,
* while replacing DataType<T1> with DataType<T2> (which may break source and binary compatibility)
* may not, and vice versa.
*
* Data types are compatible if
* * d1 is [DataType.NotNull.Simple] and d2 is [DataType.NotNull.Simple] and `d1.kind == d2.kind`
* * d1 is [DataType.NotNull.Collect] and d2 is [DataType.NotNull.Collect] and d1.elementType is compatible with d2.elementType
* * d1 is [DataType.NotNull.Partial] and d2 is [DataType.NotNull.Partial] and d1.schema is compatible to d2.schema
* (schemas s1 and s2 are considered to be compatible when they have the same number of fields,
* for each n s1.fields[n] has type compatible to s2.fields[n],
* and, depending on underlying serialization machinery,
* s1.fields[n] has either same name or same ordinal as s2.fields[n].)
*/
// Unfortunately, I can't implement Ilk by DataType: it would be impossible
// to narrow down DT parameter value in subclasses due to https://youtrack.jetbrains.com/issue/KT-13380.
// I could write @Suppress("INCONSISTENT_TYPE_PARAMETER_VALUES") but this would be required
// on every subclass including user-side `Schema`s which is horrible and inappropriate.
sealed class DataType<T> {
// “accidental” override for Ilk.custom
val custom: CustomType<T>? get() = null
/**
* Adds nullability to both runtime and stored representation of [actualType].
*
* The main reason why you can't have custom nullability is that
* lens concatenation `toPartial / toSomething` or `toPartial / toSomethingNullable`
* must produce predictable output for both runtime and stored representation.
*
* (However, some [NotNull.Simple], [NotNull.Collect], or [NotNull.Partial] implementations
* may have nullable in-memory representation, and thus cannot be wrapped into [Nullable])
*/
class Nullable<T : Any, DT : NotNull<T>>(
/**
* Wrapped non-nullable type.
*/
@JvmField val actualType: DT
) : DataType<T?>(), Ilk<T?, Nullable<T, DT>> {
init {
if (actualType is Nullable<*, *>) throw ClassCastException() // unchecked cast?..
}
override val type: Nullable<T, DT> get() = this
}
// Unfortunately, I can't insert a supertype seamlessly.
// Nesting required: https://youtrack.jetbrains.com/issue/KT-13495
// There's also no local type aliases.
@Deprecated("moved", ReplaceWith("DataType.NotNull.Simple<T>(kind)"))
abstract class Simple<T>(kind: NotNull.Simple.Kind) : NotNull.Simple<T>(kind) {
@Deprecated("moved", ReplaceWith("DataType.NotNull.Simple.Kind"))
object Kind {
inline val Bool get() = NotNull.Simple.Kind.Bool
inline val I32 get() = NotNull.Simple.Kind.I32
inline val I64 get() = NotNull.Simple.Kind.I64
inline val F32 get() = NotNull.Simple.Kind.F32
inline val F64 get() = NotNull.Simple.Kind.F64
inline val Str get() = NotNull.Simple.Kind.Str
inline val Blob get() = NotNull.Simple.Kind.Blob
}
}
@Deprecated("moved", ReplaceWith("DataType.NotNull.Collect<C, E, DE>(elementType)"))
abstract class Collect<C, E, DE : DataType<E>>(elementType: DE) : NotNull.Collect<C, E, DE>(elementType)
@Deprecated("moved", ReplaceWith("DataType.NotNull.Partial<T, SCH>(schema)"))
abstract class Partial<T, SCH : Schema<SCH>>(schema: SCH) : NotNull.Partial<T, SCH>(schema)
/**
* Common supertype for all types which cannot be stored as `null`.
*/
sealed class NotNull<T> : DataType<T>() {
/**
* A simple, non-composite (and thus easily composable) type.
*/
abstract class Simple<T>(
/**
* Specifies exact type of stored values.
*/
@JvmField val kind: Kind
) : NotNull<T>(), Ilk<T, Simple<T>> {
enum class Kind {
Bool,
@Deprecated("does not look very useful", level = DeprecationLevel.ERROR) I8,
@Deprecated("does not look very useful", level = DeprecationLevel.ERROR) I16,
I32, I64, // TODO: U32, U64, BigInt, BigFloat
F32, F64,
Str, Blob,
}
/**
* If true, string-based serialization formats will call
* [storeAsString] instead of [store], and will pass [CharSequence] to [load].
* This is useful to override appearance of numbers or blobs in JSON, XML, etc.
*/
open val hasStringRepresentation: Boolean get() = false
/**
* Converts a simple persistable value into its in-memory representation.
* @return in-memory representation of [value]
*/
abstract fun load(value: SimpleValue): T
/**
* Converts in-memory value into its simple persistable representation.
* @return persistable representation of [value]
*/
abstract fun store(value: T): SimpleValue
/**
* If [hasStringRepresentation], string-based serialization formats
* will call this method instead of [store].
*/
open fun storeAsString(value: T): CharSequence =
throw UnsupportedOperationException()
override val type: Simple<T> get() = this
}
/**
* A collection of elements of [elementType].
* In-memory type [C] is typically a collection, but it is not required.
* May have [List] or [Set] semantics, depending on implementations
* of both this data type and the underlying storage.
*
* Collection DataType handles only converting from/to a specified collection type,
* leaving values untouched.
*/
abstract class Collect<C, E, DE : DataType<E>>(
/**
* [DataType] of all the elements in such collections.
*/
@JvmField val elementType: DE
) : NotNull<C>(), Ilk<C, Collect<C, E, DE>> {
/**
* Converts a persistable collection value into its in-memory representation.
* Elements of input collection are already in their in-memory representation
* @return in-memory representation of [value]
*/
abstract fun load(value: AnyCollection): C
/**
* Converts in-memory value into a persistable collection.
* Values of output collection must be in their in-memory representation,
* it's caller's responsibility to convert them to persistable representation.
* @return persistable representation of [value], a collection of in-memory representations
*/
abstract fun store(value: C): AnyCollection
override val type: Collect<C, E, DE> get() = this
}
/**
* Represents a set of optional key-value mappings, according to [schema].
* [Schema] itself represents a special case of [Partial], where all mappings are required.
*/
abstract class Partial<T, SCH : Schema<SCH>> : NotNull<T>, Ilk<T, Partial<T, SCH>> {
@JvmField val schema: SCH
constructor(schema: SCH) {
this.schema = schema
}
internal constructor() { // for Schema itself
this.schema = this as SCH
}
/**
* Converts a persistable value into its in-memory representation.
* @param fields a set of fields provided within [values] array
* @param values values in their in-memory representation according to [fields] size
* 0 -> ignored
* 1 -> the value for the only field
* else -> 'packed' layout, no gaps between values
* @return in-memory representation of [fields] and their [values]
* @see net.aquadc.persistence.struct.indexOf
* @see net.aquadc.persistence.fill
*/
abstract fun load(fields: FieldSet<SCH, FieldDef<SCH, *, *>>, values: Any?): T
/**
* Returns a set of fields which have values.
* Required to parse data returned by [store] function.
*/
abstract fun fields(value: T): FieldSet<SCH, FieldDef<SCH, *, *>>
/**
* Converts in-memory value into its persistable representation.
* @param value an input value to read from
* @return all values, using the same layouts as in [load], in their unchanged, in-memory representation
* @see fields to know how to interpret the return value
*/
abstract fun store(value: T): Any?
override val type: Partial<T, SCH> get() = this
}
}
// these look useless but help using assertEquals() in tests:
final override fun equals(other: Any?): Boolean {
if (other !is DataType<*> || javaClass !== other.javaClass) return false
// class identity equality ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ guarantees the same behaviour
return when (this) {
is Nullable<*, *> -> other is Nullable<*, *> && actualType as DataType<*> == other.actualType
is NotNull.Simple -> other is NotNull.Simple<*> && kind === other.kind
is NotNull.Collect<*, *, *> -> other is NotNull.Collect<*, *, *> && elementType == other.elementType
is Schema<*> -> this === other
is NotNull.Partial<*, *> -> other is NotNull.Partial<*, *> && schema == other.schema
}
}
final override fun hashCode(): Int = when (this) {
is Nullable<*, *> -> 13 * actualType.hashCode()
is NotNull.Simple -> 31 * kind.hashCode()
is NotNull.Collect<*, *, *> -> 63 * elementType.hashCode()
is Schema<*> -> System.identityHashCode(this)
is NotNull.Partial<*, *> -> 127 * schema.hashCode()
}
final override fun toString(): String = when (this) {
is Nullable<*, *> -> "nullable($actualType)"
is NotNull.Simple -> kind.toString()
is NotNull.Collect<*, *, *> -> "collection($elementType)"
is Schema<*> -> javaClass.simpleName
is NotNull.Partial<*, *> -> "partial($schema)"
}
// abstract class Dictionary<M, K, V> internal constructor(keyType: DataType<K>, valueType: DataType<K>) : DataType<M>(isNullable) TODO
}
/**
* A custom type.
* Used by :sql to take advantage of native types (e.g. Postgres `uuid`, `point` etc) directly.
*
* Very similar to `TwoWay` interface from :properties,
* but :persistence don't have common dependency with :properties.
*/
abstract class CustomType<T>(
@JvmField val name: CharSequence
) : (T) -> Any? {
abstract fun back(p: Any?): T
}