mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
[wpiutil] Record/Enum struct generation fix (#7538)
ProceduralStructGenerator's genRecord and genEnum were package-private, and only extractClassStruct was made public. However, this package private visibility rendered them unable to be used by the rest of wpilib(and advanced users). Here, ProceduralStructGenerator is split into 2 classes: StructGenerator(which generates structs) and StructFetcher(the new namespace for extractClassStruct). In addition, genRecord and genEnum have been made public methods.
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.util.struct;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A utility class for fetching the assigned struct of existing classes. These are usually public,
|
||||
* static, and final properties with the Struct type.
|
||||
*/
|
||||
public final class StructFetcher {
|
||||
private StructFetcher() {
|
||||
throw new UnsupportedOperationException("This is a utility class!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Struct} for the given {@link StructSerializable} marked class. Due to the
|
||||
* non-contractual nature of the marker this can fail. If the {@code struct} field could not be
|
||||
* accessed for any reason, an empty {@link Optional} is returned.
|
||||
*
|
||||
* @param <T> The type of the class.
|
||||
* @param clazz The class object to extract the struct from.
|
||||
* @return An optional containing the struct if it could be extracted.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends StructSerializable> Optional<Struct<T>> fetchStruct(
|
||||
Class<? extends T> clazz) {
|
||||
try {
|
||||
var possibleField = Optional.ofNullable(clazz.getDeclaredField("struct"));
|
||||
return possibleField.flatMap(
|
||||
field -> {
|
||||
if (Struct.class.isAssignableFrom(field.getType())) {
|
||||
try {
|
||||
return Optional.ofNullable((Struct<T>) field.get(null));
|
||||
} catch (IllegalAccessException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
});
|
||||
} catch (NoSuchFieldException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Struct} for the given class. This does not do compile time checking that the
|
||||
* class is a {@link StructSerializable}. Whenever possible it is reccomended to use {@link
|
||||
* #fetchStruct(Class)}.
|
||||
*
|
||||
* @param clazz The class object to extract the struct from.
|
||||
* @return An optional containing the struct if it could be extracted.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Optional<Struct<?>> fetchStructDynamic(Class<?> clazz) {
|
||||
if (StructSerializable.class.isAssignableFrom(clazz)) {
|
||||
return fetchStruct((Class<? extends StructSerializable>) clazz).map(struct -> struct);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,11 +12,10 @@ import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/** A utility class for procedurally generating {@link Struct}s from records and enums. */
|
||||
public final class ProceduralStructGenerator {
|
||||
private ProceduralStructGenerator() {
|
||||
public final class StructGenerator {
|
||||
private StructGenerator() {
|
||||
throw new UnsupportedOperationException("This is a utility class!");
|
||||
}
|
||||
|
||||
@@ -123,54 +122,6 @@ public final class ProceduralStructGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Struct} for the given {@link StructSerializable} marked class. Due to the
|
||||
* non-contractual nature of the marker this can fail. If the {@code struct} field could not be
|
||||
* accessed for any reason, an empty {@link Optional} is returned.
|
||||
*
|
||||
* @param <T> The type of the class.
|
||||
* @param clazz The class object to extract the struct from.
|
||||
* @return An optional containing the struct if it could be extracted.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends StructSerializable> Optional<Struct<T>> extractClassStruct(
|
||||
Class<? extends T> clazz) {
|
||||
try {
|
||||
var possibleField = Optional.ofNullable(clazz.getDeclaredField("struct"));
|
||||
return possibleField.flatMap(
|
||||
field -> {
|
||||
if (Struct.class.isAssignableFrom(field.getType())) {
|
||||
try {
|
||||
return Optional.ofNullable((Struct<T>) field.get(null));
|
||||
} catch (IllegalAccessException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
});
|
||||
} catch (NoSuchFieldException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Struct} for the given class. This does not do compile time checking that the
|
||||
* class is a {@link StructSerializable}. Whenever possible it is reccomended to use {@link
|
||||
* #extractClassStruct(Class)}.
|
||||
*
|
||||
* @param clazz The class object to extract the struct from.
|
||||
* @return An optional containing the struct if it could be extracted.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Optional<Struct<?>> extractClassStructDynamic(Class<?> clazz) {
|
||||
if (StructSerializable.class.isAssignableFrom(clazz)) {
|
||||
return extractClassStruct((Class<? extends StructSerializable>) clazz).map(struct -> struct);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/** A utility for building schema syntax in a procedural manner. */
|
||||
@SuppressWarnings("PMD.AvoidStringBufferField")
|
||||
public static class SchemaBuilder {
|
||||
@@ -303,7 +254,7 @@ public final class ProceduralStructGenerator {
|
||||
* @return The generated struct.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "PMD.AvoidAccessibilityAlteration"})
|
||||
static <R extends Record> Struct<R> genRecord(final Class<R> recordClass) {
|
||||
public static <R extends Record> Struct<R> genRecord(final Class<R> recordClass) {
|
||||
final RecordComponent[] components = recordClass.getRecordComponents();
|
||||
final SchemaBuilder schemaBuilder = new SchemaBuilder();
|
||||
final ArrayList<Struct<?>> nestedStructs = new ArrayList<>();
|
||||
@@ -329,7 +280,7 @@ public final class ProceduralStructGenerator {
|
||||
if (customStructTypeMap.containsKey(type)) {
|
||||
struct = customStructTypeMap.get(type);
|
||||
} else if (StructSerializable.class.isAssignableFrom(type)) {
|
||||
var optStruct = extractClassStructDynamic(type);
|
||||
var optStruct = StructFetcher.fetchStructDynamic(type);
|
||||
if (optStruct.isPresent()) {
|
||||
struct = optStruct.get();
|
||||
} else {
|
||||
@@ -465,7 +416,7 @@ public final class ProceduralStructGenerator {
|
||||
* @return The generated struct.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "PMD.AvoidAccessibilityAlteration"})
|
||||
static <E extends Enum<E>> Struct<E> genEnum(Class<E> enumClass) {
|
||||
public static <E extends Enum<E>> Struct<E> genEnum(Class<E> enumClass) {
|
||||
final E[] enumVariants = enumClass.getEnumConstants();
|
||||
final Field[] allEnumFields = enumClass.getDeclaredFields();
|
||||
final SchemaBuilder schemaBuilder = new SchemaBuilder();
|
||||
@@ -516,7 +467,7 @@ public final class ProceduralStructGenerator {
|
||||
if (customStructTypeMap.containsKey(type)) {
|
||||
struct = customStructTypeMap.get(type);
|
||||
} else if (StructSerializable.class.isAssignableFrom(type)) {
|
||||
var optStruct = extractClassStructDynamic(type);
|
||||
var optStruct = StructFetcher.fetchStructDynamic(type);
|
||||
if (optStruct.isPresent()) {
|
||||
struct = optStruct.get();
|
||||
} else {
|
||||
@@ -4,15 +4,15 @@
|
||||
|
||||
package edu.wpi.first.util.struct;
|
||||
|
||||
import static edu.wpi.first.util.struct.ProceduralStructGenerator.genEnum;
|
||||
import static edu.wpi.first.util.struct.ProceduralStructGenerator.genRecord;
|
||||
import static edu.wpi.first.util.struct.StructGenerator.genEnum;
|
||||
import static edu.wpi.first.util.struct.StructGenerator.genRecord;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ProceduralStructGeneratorTest {
|
||||
class StructGeneratorTest {
|
||||
public record CustomRecord(int int32, boolean bool, double float64, char character, short int16)
|
||||
implements StructSerializable {
|
||||
public static CustomRecord create() {
|
||||
@@ -95,8 +95,7 @@ class ProceduralStructGeneratorTest {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <S extends StructSerializable> void testStructRoundTrip(S value) {
|
||||
Struct<S> struct =
|
||||
ProceduralStructGenerator.extractClassStruct((Class<S>) value.getClass()).get();
|
||||
Struct<S> struct = StructFetcher.fetchStruct((Class<S>) value.getClass()).get();
|
||||
ByteBuffer buffer = ByteBuffer.allocate(struct.getSize());
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
struct.pack(buffer, value);
|
||||
@@ -108,8 +107,7 @@ class ProceduralStructGeneratorTest {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <S extends StructSerializable> void testStructDoublePack(S value) {
|
||||
Struct<S> struct =
|
||||
ProceduralStructGenerator.extractClassStruct((Class<S>) value.getClass()).get();
|
||||
Struct<S> struct = StructFetcher.fetchStruct((Class<S>) value.getClass()).get();
|
||||
ByteBuffer buffer = ByteBuffer.allocate(struct.getSize());
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
struct.pack(buffer, value);
|
||||
@@ -123,8 +121,7 @@ class ProceduralStructGeneratorTest {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <S extends StructSerializable> void testStructDoubleUnpack(S value) {
|
||||
Struct<S> struct =
|
||||
ProceduralStructGenerator.extractClassStruct((Class<S>) value.getClass()).get();
|
||||
Struct<S> struct = StructFetcher.fetchStruct((Class<S>) value.getClass()).get();
|
||||
ByteBuffer buffer = ByteBuffer.allocate(struct.getSize());
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
struct.pack(buffer, value);
|
||||
Reference in New Issue
Block a user