Skip to content

Commit

Permalink
initial
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel1464 committed Dec 11, 2024
1 parent cc41a0e commit ed3d547
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 61 deletions.
62 changes: 62 additions & 0 deletions wpiutil/src/main/java/edu/wpi/first/util/struct/StructFetcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@
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!");
}

Expand Down Expand Up @@ -123,54 +122,6 @@ public static <T> void addCustomStruct(Class<T> clazz, Struct<T> struct, boolean
}
}

/**
* 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 {
Expand Down Expand Up @@ -303,7 +254,7 @@ public boolean isImmutable() {
* @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<>();
Expand All @@ -329,7 +280,7 @@ static <R extends Record> Struct<R> genRecord(final Class<R> recordClass) {
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 {
Expand Down Expand Up @@ -465,7 +416,7 @@ public boolean isImmutable() {
* @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();
Expand Down Expand Up @@ -516,7 +467,7 @@ static <E extends Enum<E>> Struct<E> genEnum(Class<E> enumClass) {
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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -96,7 +96,7 @@ public final int hashCode() {
@SuppressWarnings("unchecked")
private <S extends StructSerializable> void testStructRoundTrip(S value) {
Struct<S> struct =
ProceduralStructGenerator.extractClassStruct((Class<S>) value.getClass()).get();
StructFetcher.fetchStruct((Class<S>) value.getClass()).get();
ByteBuffer buffer = ByteBuffer.allocate(struct.getSize());
buffer.order(ByteOrder.LITTLE_ENDIAN);
struct.pack(buffer, value);
Expand All @@ -109,7 +109,7 @@ private <S extends StructSerializable> void testStructRoundTrip(S value) {
@SuppressWarnings("unchecked")
private <S extends StructSerializable> void testStructDoublePack(S value) {
Struct<S> struct =
ProceduralStructGenerator.extractClassStruct((Class<S>) value.getClass()).get();
StructFetcher.fetchStruct((Class<S>) value.getClass()).get();
ByteBuffer buffer = ByteBuffer.allocate(struct.getSize());
buffer.order(ByteOrder.LITTLE_ENDIAN);
struct.pack(buffer, value);
Expand All @@ -124,7 +124,7 @@ private <S extends StructSerializable> void testStructDoublePack(S value) {
@SuppressWarnings("unchecked")
private <S extends StructSerializable> void testStructDoubleUnpack(S value) {
Struct<S> struct =
ProceduralStructGenerator.extractClassStruct((Class<S>) value.getClass()).get();
StructFetcher.fetchStruct((Class<S>) value.getClass()).get();
ByteBuffer buffer = ByteBuffer.allocate(struct.getSize());
buffer.order(ByteOrder.LITTLE_ENDIAN);
struct.pack(buffer, value);
Expand Down

0 comments on commit ed3d547

Please sign in to comment.