Skip to content

Commit 5db4440

Browse files
committed
Add a way to describe fields regardless of their storage and format
Whether the month is formatted as a number or a name, and whether it's stored in `LocalDate` or `LocalDateTime`, we still say that this field represents a year. Each `FieldSpec` instance is a distinct such field.
1 parent 27b52ca commit 5db4440

File tree

1 file changed

+148
-0
lines changed

1 file changed

+148
-0
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright 2019-2023 JetBrains s.r.o. and contributors.
3+
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
4+
*/
5+
6+
package kotlinx.datetime.internal.format
7+
8+
import kotlinx.datetime.internal.format.parser.AssignableField
9+
import kotlin.reflect.KMutableProperty1
10+
11+
/**
12+
* A lens-like interface for accessing fields of objects.
13+
*/
14+
internal interface Accessor<in Object, Field>: AssignableField<Object, Field> {
15+
/**
16+
* The value of the field in the given object, or `null` if the field is not set.
17+
*/
18+
fun getter(container: Object): Field?
19+
20+
/**
21+
* Function that returns the value of the field in the given object,
22+
* or throws [IllegalStateException] if the field is not set.
23+
*
24+
* This function is used to access fields during formatting.
25+
*/
26+
fun getterNotNull(container: Object): Field =
27+
getter(container) ?: throw IllegalStateException("Field $name is not set")
28+
}
29+
30+
/**
31+
* An implementation of [Accessor] for a mutable property of an object.
32+
*/
33+
internal class PropertyAccessor<Object, Field>(private val property: KMutableProperty1<Object, Field?>): Accessor<Object, Field> {
34+
override val name: String get() = property.name
35+
36+
override fun trySetWithoutReassigning(container: Object, newValue: Field): Field? {
37+
val oldValue = property.get(container)
38+
return when {
39+
oldValue === null -> {
40+
property.set(container, newValue)
41+
null
42+
}
43+
oldValue == newValue -> null
44+
else -> oldValue
45+
}
46+
}
47+
48+
override fun getter(container: Object): Field? = property.get(container)
49+
}
50+
51+
/**
52+
* A description of the field's numeric sign.
53+
*
54+
* Several fields can share the same sign. For example, the hour and the minute of the UTC offset have the same sign,
55+
* and setting the sign of the hour also sets the sign of the minute.
56+
*
57+
* Implementations of this interface are *not* required to redefine [equals] and [hashCode].
58+
* Instead, signs should be defined as singletons and compared by reference.
59+
*/
60+
internal interface FieldSign<in Target> {
61+
/**
62+
* The field that is `true` if the value of the field is known to be negative, and `false` otherwise.
63+
* Can be both read from and written to.
64+
*/
65+
val isNegative: Accessor<Target, Boolean>
66+
67+
/**
68+
* A check for whether the current value of the field is zero.
69+
*/
70+
fun isZero(obj: Target): Boolean
71+
}
72+
73+
/**
74+
* A specification of a field.
75+
*
76+
* Fields represent parts of objects, regardless of how they are stored or formatted.
77+
* For example, a field "day of week" can be represented with both strings of various kinds ("Monday", "Mon") and
78+
* numbers ("1" for Monday, "2" for Tuesday, etc.), but the field itself is the same.
79+
*
80+
* Fields can typically contain `null` values, which means that the field is not set.
81+
*/
82+
internal interface FieldSpec<in Target, Type> {
83+
/**
84+
* The function with which the field can be accessed.
85+
*/
86+
val accessor: Accessor<Target, Type>
87+
88+
/**
89+
* The default value of the field, or `null` if the field has none.
90+
*/
91+
val defaultValue: Type?
92+
93+
/**
94+
* The name of the field.
95+
*/
96+
val name: String
97+
98+
/**
99+
* The sign corresponding to the field value, or `null` if the field has none.
100+
*/
101+
val sign: FieldSign<Target>?
102+
}
103+
104+
/**
105+
* Inherit from this class to obtain a sensible [toString] implementation for debugging.
106+
*/
107+
internal abstract class AbstractFieldSpec<in Target, Type>: FieldSpec<Target, Type> {
108+
override fun toString(): String = "The field $name (default value is $defaultValue)"
109+
}
110+
111+
/**
112+
* A specification of a field that can contain values of any kind.
113+
* Used for fields additional information about which is not that important for parsing/formatting.
114+
*/
115+
internal class GenericFieldSpec<in Target, Type>(
116+
override val accessor: Accessor<Target, Type>,
117+
override val name: String = accessor.name,
118+
override val defaultValue: Type? = null,
119+
override val sign: FieldSign<Target>? = null,
120+
) : AbstractFieldSpec<Target, Type>()
121+
122+
/**
123+
* A specification of a field that can only contain non-negative numeric values.
124+
*/
125+
internal class UnsignedFieldSpec<in Target>(
126+
override val accessor: Accessor<Target, Int>,
127+
/**
128+
* The minimum value of the field.
129+
*/
130+
val minValue: Int,
131+
/**
132+
* The maximum value of the field.
133+
*/
134+
val maxValue: Int,
135+
override val name: String = accessor.name,
136+
override val defaultValue: Int? = null,
137+
override val sign: FieldSign<Target>? = null,
138+
) : AbstractFieldSpec<Target, Int>() {
139+
/**
140+
* The maximum length of the field when represented as a decimal number.
141+
*/
142+
val maxDigits: Int = when {
143+
maxValue < 10 -> 1
144+
maxValue < 100 -> 2
145+
maxValue < 1000 -> 3
146+
else -> throw IllegalArgumentException("Max value $maxValue is too large")
147+
}
148+
}

0 commit comments

Comments
 (0)