Skip to content

Commit 089788b

Browse files
authored
Add PersistedConfiguration.fromPreferences(String, Class) (#38)
This can simplify the code required to make a value configurable, as the caller does not need to specify default values, create a builder, etc.
1 parent 00e9cd5 commit 089788b

File tree

2 files changed

+330
-65
lines changed

2 files changed

+330
-65
lines changed

lib/src/main/java/com/team2813/lib2813/preferences/PersistedConfiguration.java

+153-50
Original file line numberDiff line numberDiff line change
@@ -16,43 +16,70 @@
1616
/**
1717
* Initializes the fields of a Record Class from values stored in {@link Preferences}.
1818
*
19+
* <p>The Preference values can be updated in the SmartDashboard and/or Shuffleboard UI; updated
20+
* values will be stored in the flash storage for the robot.
21+
*
1922
* <p>Example use:
2023
*
21-
* {@snippet :
24+
* <pre>{@code
2225
* public final class Drive {
2326
*
2427
* public record DriveConfiguration(
2528
* boolean addVisionMeasurements, long robotWeight,
2629
* DoubleSupplier powerMultiplier) {
2730
*
2831
* public static DriveConfiguration fromPreferences() {
29-
* DriveConfiguration defaultConfig = new DriveConfiguration(
30-
* true, 1337, () -> 3.14);
31-
* return PersistedConfiguration.fromPreferences("Drive", defaultConfig);
32+
* return PersistedConfiguration.fromPreferences("Drive", DriveConfiguration.class);
3233
* }
3334
* }
3435
* }
35-
* }
36+
* }</pre>
3637
*
3738
* <p>In the above example, {@code fromPreferences()} would return a record instance with the values
3839
* populated the "Preferences" NetworkTables table. The keys would be:
3940
*
4041
* <ul>
41-
* <li>{@code "Drive.DriveConfiguration.addLimelightMeasurement"}
42-
* <li>{@code "Drive.DriveConfiguration.robotWeight"}
43-
* <li>{@code "Drive.DriveConfiguration.powerMultiplier"}
42+
* <li>{@code "Drive/addVisionMeasurements"}
43+
* <li>{@code "Drive/robotWeight"}
44+
* <li>{@code "Drive/powerMultiplier"}
4445
* </ul>
4546
*
46-
* <p>The default values of for these Preference values will be the values provided to {@link
47-
* PersistedConfiguration#fromPreferences(String, Record)}. The values can be updated in the
48-
* SmartDashboard and/or Shuffleboard UI; updated values will be stored in the flash storage for the
49-
* robot. In the above example, if none of the above preference keys existed, preferences will be
50-
* created with the following values:
47+
* <p>If no value is stored in Preferences for a key, the default value returned (and initialized in
48+
* Preferences) would be the default value for the type of the record component. In the above
49+
* example, if none of the above preference keys existed, preferences will be created with the
50+
* following values:
5151
*
5252
* <ul>
53-
* <li>{@code "Drive.DriveConfiguration.addLimelightMeasurement"}: {@code true}
54-
* <li>{@code "Drive.DriveConfiguration.robotWeight"}: 1337
55-
* <li>{@code "Drive.DriveConfiguration.powerMultiplier"}: 3.14
53+
* <li>{@code "Drive/addVisionMeasurements"}: {@code false}
54+
* <li>{@code "Drive/robotWeight"}: {@code 0}
55+
* <li>{@code "Drive/powerMultiplier"}: {@code 0.0}
56+
* </ul>
57+
*
58+
* <p>The caller could specify different default values by passing an instance of the record class:
59+
*
60+
* <pre>{@code
61+
* public final class Drive {
62+
*
63+
* public record DriveConfiguration(
64+
* boolean addVisionMeasurements, long robotWeight,
65+
* DoubleSupplier maxAngularVelocity) {
66+
*
67+
* public static DriveConfiguration fromPreferences() {
68+
* DriveConfiguration defaultConfig = new DriveConfiguration(
69+
* true, 2813, () -> 3.14);
70+
* return PersistedConfiguration.fromPreferences("Drive", defaultConfig);
71+
* }
72+
* }
73+
* }
74+
* }</pre>
75+
*
76+
* <p>In the above example, {@code fromPreferences()} would return a record instance with the values
77+
* populated the "Preferences" NetworkTables table. The keys and default values would be:
78+
*
79+
* <ul>
80+
* <li>{@code "Drive/addVisionMeasurements"} (default value: {@code true})
81+
* <li>{@code "Drive/robotWeight"} (default value: {@code 2813})
82+
* <li>{@code "Drive/maxAngularVelocity"} (default value: {@code 3.14})
5683
* </ul>
5784
*
5885
* <p>For record classes with many component values of the same type, it is strongly recommended
@@ -64,6 +91,8 @@
6491
* constructor to create record instances, so any parameter validation should be done in a custom
6592
* constructor; see <a href="https://www.baeldung.com/java-records-custom-constructor">Custom
6693
* Constructor in Java Records</a> for details.
94+
*
95+
* @since 1.3.0
6796
*/
6897
public final class PersistedConfiguration {
6998
// The below package-scope fields are for the self-tests.
@@ -99,11 +128,41 @@ public static <T extends Record> T fromPreferences(String preferenceName, T conf
99128
return fromPreferences(preferenceName, configWithDefaults, PATH_SEPARATOR);
100129
}
101130

131+
/**
132+
* Creates a record class instance of the provided type, with fields populated from Preferences.
133+
*
134+
* <p>To be stored in preferences, the type of the record components can be any of the following:
135+
*
136+
* <ul>
137+
* <li>{@code boolean} or {@code BooleanSupplier} or {@code Supplier<Boolean>}
138+
* <li>{@code int} or {@code IntSupplier} or {@code Supplier<Integer>}
139+
* <li>{@code long} or {@code LongSupplier} or {@code Supplier<Long>}
140+
* <li>{@code double} or {@code DoubleSupplier} or {@code Supplier<Double>}
141+
* <li>{@code String} or {@code Supplier<String>}
142+
* <li>{@code Record} following the above rules
143+
* </ul>
144+
*
145+
* <p>The default values for the preferences will be Java defaults (for example, zero for
146+
* integers).
147+
*
148+
* @param preferenceName Preference subtable to use to get the values.
149+
* @param recordClass Type of the record instance to populate from preferences.
150+
* @throws IllegalArgumentException If {@code preferenceName} is empty or contains a {@code '/'}.
151+
* @throws IllegalStateException If {@code preferenceName} was used for a different record class.
152+
*/
153+
public static <T extends Record> T fromPreferences(String preferenceName, Class<T> recordClass) {
154+
return fromPreferences(preferenceName, recordClass, null, PATH_SEPARATOR);
155+
}
156+
102157
static <T extends Record> T fromPreferences(
103158
String preferenceName, T configWithDefaults, char pathSeparator) {
104159
@SuppressWarnings("unchecked")
105160
Class<T> recordClass = (Class<T>) configWithDefaults.getClass();
161+
return fromPreferences(preferenceName, recordClass, configWithDefaults, pathSeparator);
162+
}
106163

164+
private static <T extends Record> T fromPreferences(
165+
String preferenceName, Class<T> recordClass, T configWithDefaults, char pathSeparator) {
107166
NetworkTableInstance ntInstance = NetworkTableInstance.getDefault();
108167
validatePreferenceName(preferenceName);
109168
verifyNotRegisteredToAnotherClass(ntInstance, preferenceName, recordClass);
@@ -196,7 +255,9 @@ private static <T> T createFromPreferences(
196255
params[i] = componentValue;
197256
} else {
198257
// Fetch the value from Preferences
199-
params[i] = factory.create(component, key, componentValue);
258+
params[i] =
259+
factory.create(
260+
component, key, componentValue, /* initializePreference= */ needComponentValue);
200261
}
201262
i++;
202263
}
@@ -207,20 +268,22 @@ private static <T> T createFromPreferences(
207268

208269
@FunctionalInterface
209270
private interface PreferenceFactory {
210-
Object create(RecordComponent component, String key, Object defaultValue);
271+
Object create(
272+
RecordComponent component, String key, Object defaultValue, boolean initializePreference);
211273
}
212274

213275
@FunctionalInterface
214276
private interface GenericPreferenceFactory<T> {
215-
T create(RecordComponent component, String key, T defaultValue);
277+
T create(RecordComponent component, String key, T defaultValue, boolean initializePreference);
216278
}
217279

218280
private static final Map<Type, PreferenceFactory> TYPE_TO_FACTORY = new HashMap<>();
219281

220282
@SuppressWarnings("unchecked")
221283
private static <T> void register(Class<T> type, GenericPreferenceFactory<T> simpleFactory) {
222284
PreferenceFactory factory =
223-
(component, key, defaultValue) -> simpleFactory.create(component, key, (T) defaultValue);
285+
(component, key, defaultValue, initializePreference) ->
286+
simpleFactory.create(component, key, (T) defaultValue, initializePreference);
224287
TYPE_TO_FACTORY.put(type, factory);
225288
}
226289

@@ -248,81 +311,117 @@ private static <T> void register(Class<T> type, GenericPreferenceFactory<T> simp
248311

249312
/** Gets a boolean value from Preferences for the given component. */
250313
private static boolean booleanFactory(
251-
RecordComponent component, String key, Boolean defaultValue) {
252-
if (defaultValue != null) {
314+
RecordComponent component, String key, Boolean defaultValue, boolean initialize) {
315+
if (initialize) {
316+
if (defaultValue == null) {
317+
defaultValue = Boolean.FALSE;
318+
}
253319
Preferences.initBoolean(key, defaultValue);
320+
return defaultValue;
254321
}
255322
return Preferences.getBoolean(key, false);
256323
}
257324

258325
/** Gets a BooleanSupplier value from Preferences for the given component. */
259326
private static BooleanSupplier booleanSupplierFactory(
260-
RecordComponent component, String key, BooleanSupplier defaultValueSupplier) {
261-
if (defaultValueSupplier != null) {
262-
boolean defaultValue = defaultValueSupplier.getAsBoolean();
327+
RecordComponent component,
328+
String key,
329+
BooleanSupplier defaultValueSupplier,
330+
boolean initialize) {
331+
if (initialize) {
332+
boolean defaultValue = false;
333+
if (defaultValueSupplier != null) {
334+
defaultValue = defaultValueSupplier.getAsBoolean();
335+
}
263336
Preferences.initBoolean(key, defaultValue);
264337
}
265338
return () -> Preferences.getBoolean(key, false);
266339
}
267340

268341
/** Gets an int value from Preferences for the given component. */
269-
private static int intFactory(RecordComponent component, String key, Integer defaultValue) {
270-
if (defaultValue != null) {
342+
private static int intFactory(
343+
RecordComponent component, String key, Integer defaultValue, boolean initialize) {
344+
if (initialize) {
345+
if (defaultValue == null) {
346+
defaultValue = 0;
347+
}
271348
Preferences.initInt(key, defaultValue);
349+
return defaultValue;
272350
}
273351
return Preferences.getInt(key, 0);
274352
}
275353

276354
/** Gets a IntSupplier value from Preferences for the given component. */
277355
private static IntSupplier intSupplierFactory(
278-
RecordComponent component, String key, IntSupplier defaultValueSupplier) {
279-
if (defaultValueSupplier != null) {
280-
int defaultValue = defaultValueSupplier.getAsInt();
356+
RecordComponent component, String key, IntSupplier defaultValueSupplier, boolean initialize) {
357+
if (initialize) {
358+
int defaultValue = defaultValueSupplier != null ? defaultValueSupplier.getAsInt() : 0;
281359
Preferences.initInt(key, defaultValue);
282360
}
283361
return () -> Preferences.getInt(key, 0);
284362
}
285363

286364
/** Gets a long value from Preferences for the given component. */
287-
private static long longFactory(RecordComponent component, String key, Long defaultValue) {
288-
if (defaultValue != null) {
365+
private static long longFactory(
366+
RecordComponent component, String key, Long defaultValue, boolean initialize) {
367+
if (initialize) {
368+
if (defaultValue == null) {
369+
defaultValue = 0L;
370+
}
289371
Preferences.initLong(key, defaultValue);
372+
return defaultValue;
290373
}
291374
return Preferences.getLong(key, 0);
292375
}
293376

294377
/** Gets a LongSupplier value from Preferences for the given component. */
295378
private static LongSupplier longSupplierFactory(
296-
RecordComponent component, String key, LongSupplier defaultValueSupplier) {
297-
if (defaultValueSupplier != null) {
298-
long defaultValue = defaultValueSupplier.getAsLong();
379+
RecordComponent component,
380+
String key,
381+
LongSupplier defaultValueSupplier,
382+
boolean initialize) {
383+
if (initialize) {
384+
long defaultValue = defaultValueSupplier != null ? defaultValueSupplier.getAsLong() : 0;
299385
Preferences.initLong(key, defaultValue);
300386
}
301387
return () -> Preferences.getLong(key, 0);
302388
}
303389

304390
/** Gets a double value from Preferences for the given component. */
305-
private static double doubleFactory(RecordComponent component, String key, Double defaultValue) {
306-
if (defaultValue != null) {
391+
private static double doubleFactory(
392+
RecordComponent component, String key, Double defaultValue, boolean initialize) {
393+
if (initialize) {
394+
if (defaultValue == null) {
395+
defaultValue = 0.0;
396+
}
307397
Preferences.initDouble(key, defaultValue);
398+
return defaultValue;
308399
}
309400
return Preferences.getDouble(key, 0);
310401
}
311402

312403
/** Gets a DoubleSupplier value from Preferences for the given component. */
313404
private static DoubleSupplier doubleSupplierFactory(
314-
RecordComponent component, String key, DoubleSupplier defaultValueSupplier) {
315-
if (defaultValueSupplier != null) {
316-
double defaultValue = defaultValueSupplier.getAsDouble();
405+
RecordComponent component,
406+
String key,
407+
DoubleSupplier defaultValueSupplier,
408+
boolean initialize) {
409+
if (initialize) {
410+
double defaultValue = defaultValueSupplier != null ? defaultValueSupplier.getAsDouble() : 0;
317411
Preferences.initDouble(key, defaultValue);
318412
}
319413
return () -> Preferences.getDouble(key, 0);
320414
}
321415

322416
/** Gets a String value from Preferences for the given component. */
323-
private static String stringFactory(RecordComponent component, String key, String defaultValue) {
324-
if (defaultValue != null) {
417+
private static String stringFactory(
418+
RecordComponent component, String key, String defaultValue, boolean initialize) {
419+
if (initialize) {
420+
if (defaultValue == null) {
421+
defaultValue = "";
422+
}
325423
Preferences.initString(key, defaultValue);
424+
return defaultValue;
326425
}
327426
return Preferences.getString(key, "");
328427
}
@@ -332,7 +431,7 @@ private static String stringFactory(RecordComponent component, String key, Strin
332431
* boolean and float values.
333432
*/
334433
private static Supplier<?> supplierFactory(
335-
RecordComponent component, String key, Supplier<?> defaultValueSupplier) {
434+
RecordComponent component, String key, Supplier<?> defaultValueSupplier, boolean initialize) {
336435
Type supplierType =
337436
((ParameterizedType) component.getGenericType()).getActualTypeArguments()[0];
338437
Type registeredType = SUPPLIER_TYPE_TO_REGISTERED_TYPE.get(supplierType);
@@ -344,16 +443,20 @@ private static Supplier<?> supplierFactory(
344443
}
345444
PreferenceFactory factory = TYPE_TO_FACTORY.get(registeredType);
346445

347-
if (defaultValueSupplier != null) {
348-
Object defaultValue = defaultValueSupplier.get();
349-
if (defaultValue == null) {
350-
warn("Cannot store '%s' in Preferences; default value is null", component.getName());
351-
return defaultValueSupplier;
446+
if (initialize) {
447+
Object defaultValue = null;
448+
if (defaultValueSupplier != null) {
449+
defaultValue = defaultValueSupplier.get();
450+
if (defaultValue == null) {
451+
warn("Cannot store '%s' in Preferences; default value is null", component.getName());
452+
return defaultValueSupplier;
453+
}
352454
}
353-
factory.create(component, key, defaultValue); // Call Preferences.init{String,Double,etc}()
455+
factory.create(
456+
component, key, defaultValue, true); // Call Preferences.init{String,Double,etc}()
354457
}
355458

356-
return () -> factory.create(component, key, null);
459+
return () -> factory.create(component, key, null, false);
357460
}
358461

359462
private static void warn(String format, Object... args) {

0 commit comments

Comments
 (0)