16
16
/**
17
17
* Initializes the fields of a Record Class from values stored in {@link Preferences}.
18
18
*
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
+ *
19
22
* <p>Example use:
20
23
*
21
- * {@snippet :
24
+ * <pre>{@code
22
25
* public final class Drive {
23
26
*
24
27
* public record DriveConfiguration(
25
28
* boolean addVisionMeasurements, long robotWeight,
26
29
* DoubleSupplier powerMultiplier) {
27
30
*
28
31
* 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);
32
33
* }
33
34
* }
34
35
* }
35
- * }
36
+ * }</pre>
36
37
*
37
38
* <p>In the above example, {@code fromPreferences()} would return a record instance with the values
38
39
* populated the "Preferences" NetworkTables table. The keys would be:
39
40
*
40
41
* <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"}
44
45
* </ul>
45
46
*
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:
51
51
*
52
52
* <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})
56
83
* </ul>
57
84
*
58
85
* <p>For record classes with many component values of the same type, it is strongly recommended
64
91
* constructor to create record instances, so any parameter validation should be done in a custom
65
92
* constructor; see <a href="https://www.baeldung.com/java-records-custom-constructor">Custom
66
93
* Constructor in Java Records</a> for details.
94
+ *
95
+ * @since 1.3.0
67
96
*/
68
97
public final class PersistedConfiguration {
69
98
// 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
99
128
return fromPreferences (preferenceName , configWithDefaults , PATH_SEPARATOR );
100
129
}
101
130
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
+
102
157
static <T extends Record > T fromPreferences (
103
158
String preferenceName , T configWithDefaults , char pathSeparator ) {
104
159
@ SuppressWarnings ("unchecked" )
105
160
Class <T > recordClass = (Class <T >) configWithDefaults .getClass ();
161
+ return fromPreferences (preferenceName , recordClass , configWithDefaults , pathSeparator );
162
+ }
106
163
164
+ private static <T extends Record > T fromPreferences (
165
+ String preferenceName , Class <T > recordClass , T configWithDefaults , char pathSeparator ) {
107
166
NetworkTableInstance ntInstance = NetworkTableInstance .getDefault ();
108
167
validatePreferenceName (preferenceName );
109
168
verifyNotRegisteredToAnotherClass (ntInstance , preferenceName , recordClass );
@@ -196,7 +255,9 @@ private static <T> T createFromPreferences(
196
255
params [i ] = componentValue ;
197
256
} else {
198
257
// 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 );
200
261
}
201
262
i ++;
202
263
}
@@ -207,20 +268,22 @@ private static <T> T createFromPreferences(
207
268
208
269
@ FunctionalInterface
209
270
private interface PreferenceFactory {
210
- Object create (RecordComponent component , String key , Object defaultValue );
271
+ Object create (
272
+ RecordComponent component , String key , Object defaultValue , boolean initializePreference );
211
273
}
212
274
213
275
@ FunctionalInterface
214
276
private interface GenericPreferenceFactory <T > {
215
- T create (RecordComponent component , String key , T defaultValue );
277
+ T create (RecordComponent component , String key , T defaultValue , boolean initializePreference );
216
278
}
217
279
218
280
private static final Map <Type , PreferenceFactory > TYPE_TO_FACTORY = new HashMap <>();
219
281
220
282
@ SuppressWarnings ("unchecked" )
221
283
private static <T > void register (Class <T > type , GenericPreferenceFactory <T > simpleFactory ) {
222
284
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 );
224
287
TYPE_TO_FACTORY .put (type , factory );
225
288
}
226
289
@@ -248,81 +311,117 @@ private static <T> void register(Class<T> type, GenericPreferenceFactory<T> simp
248
311
249
312
/** Gets a boolean value from Preferences for the given component. */
250
313
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
+ }
253
319
Preferences .initBoolean (key , defaultValue );
320
+ return defaultValue ;
254
321
}
255
322
return Preferences .getBoolean (key , false );
256
323
}
257
324
258
325
/** Gets a BooleanSupplier value from Preferences for the given component. */
259
326
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
+ }
263
336
Preferences .initBoolean (key , defaultValue );
264
337
}
265
338
return () -> Preferences .getBoolean (key , false );
266
339
}
267
340
268
341
/** 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
+ }
271
348
Preferences .initInt (key , defaultValue );
349
+ return defaultValue ;
272
350
}
273
351
return Preferences .getInt (key , 0 );
274
352
}
275
353
276
354
/** Gets a IntSupplier value from Preferences for the given component. */
277
355
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 ;
281
359
Preferences .initInt (key , defaultValue );
282
360
}
283
361
return () -> Preferences .getInt (key , 0 );
284
362
}
285
363
286
364
/** 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
+ }
289
371
Preferences .initLong (key , defaultValue );
372
+ return defaultValue ;
290
373
}
291
374
return Preferences .getLong (key , 0 );
292
375
}
293
376
294
377
/** Gets a LongSupplier value from Preferences for the given component. */
295
378
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 ;
299
385
Preferences .initLong (key , defaultValue );
300
386
}
301
387
return () -> Preferences .getLong (key , 0 );
302
388
}
303
389
304
390
/** 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
+ }
307
397
Preferences .initDouble (key , defaultValue );
398
+ return defaultValue ;
308
399
}
309
400
return Preferences .getDouble (key , 0 );
310
401
}
311
402
312
403
/** Gets a DoubleSupplier value from Preferences for the given component. */
313
404
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 ;
317
411
Preferences .initDouble (key , defaultValue );
318
412
}
319
413
return () -> Preferences .getDouble (key , 0 );
320
414
}
321
415
322
416
/** 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
+ }
325
423
Preferences .initString (key , defaultValue );
424
+ return defaultValue ;
326
425
}
327
426
return Preferences .getString (key , "" );
328
427
}
@@ -332,7 +431,7 @@ private static String stringFactory(RecordComponent component, String key, Strin
332
431
* boolean and float values.
333
432
*/
334
433
private static Supplier <?> supplierFactory (
335
- RecordComponent component , String key , Supplier <?> defaultValueSupplier ) {
434
+ RecordComponent component , String key , Supplier <?> defaultValueSupplier , boolean initialize ) {
336
435
Type supplierType =
337
436
((ParameterizedType ) component .getGenericType ()).getActualTypeArguments ()[0 ];
338
437
Type registeredType = SUPPLIER_TYPE_TO_REGISTERED_TYPE .get (supplierType );
@@ -344,16 +443,20 @@ private static Supplier<?> supplierFactory(
344
443
}
345
444
PreferenceFactory factory = TYPE_TO_FACTORY .get (registeredType );
346
445
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
+ }
352
454
}
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}()
354
457
}
355
458
356
- return () -> factory .create (component , key , null );
459
+ return () -> factory .create (component , key , null , false );
357
460
}
358
461
359
462
private static void warn (String format , Object ... args ) {
0 commit comments