Files
pcinv/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java
T
seeseemelk 8e26a73243
Build / build (push) Successful in 2m10s
Add build management features and views
2025-06-15 09:03:47 +02:00

226 lines
8.8 KiB
Java

package be.seeseepuff.pcinv.meta;
import be.seeseepuff.pcinv.models.*;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import lombok.Builder;
import lombok.Getter;
import lombok.Singular;
import java.lang.reflect.Field;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;
/**
* Represents a property of an asset, such as its name or type.
* This class is used to define the metadata for assets in the system.
*/
@Getter
@Builder
public class AssetProperty {
/// The name of the property, e.g., "brand", "model", etc.
private final String name;
/// The name of the property as it should be displayed, e.g., "Brand", "Model", etc.
private final String displayName;
/// The type of the property, which can be a string or an integer.
private final PropertyType type;
/// Whether the property is required for the asset.
private final boolean required;
/// A set of options for the property, used for enum types.
@Singular
private final List<AssetOption> options;
/// Whether the capacity should be displayed in SI units (e.g., GB, MB).
private final boolean capacityAsSI;
/// Whether the capacity should be displayed in IEC units (e.g., GiB, MiB).
private final boolean capacityAsIEC;
/// A setter function that can be used to set the value of the property on an asset.
private final BiConsumer<Object, Object> setter;
/// A getter function that can be used to get the value of the property from an asset.
private final Function<Object, Object> getter;
/// Whether the property is an input list.
private final boolean inputList;
/// Whether the property should be hidden in the overview.
private final boolean hideInOverview;
/// A description of the property, if any.
private final String description;
/**
* Loads an AssetProperty from a given field.
*
* @param property The field representing the property.
* @return An AssetProperty instance with the name, display name, and type determined from the field.
*/
@Nullable
public static AssetProperty loadFrom(Field property, @Nonnull String assetType) {
var annotation = property.getAnnotation(Property.class);
if (annotation == null) {
return null;
}
var type = determineType(property);
var builder = AssetProperty.builder()
.name(property.getName())
.displayName(annotation.value())
.type(type)
.required(annotation.required())
.inputList(property.isAnnotationPresent(InputList.class))
.hideInOverview(property.isAnnotationPresent(HideInOverview.class))
.description(property.isAnnotationPresent(Description.class) ? property.getAnnotation(Description.class).value() : "")
.setter((obj, value) -> {
try {
property.setAccessible(true);
property.set(obj, Converters.convert(value, property.getType()));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
})
.getter(obj -> {
try {
if (assetType.equals(GenericAsset.TYPE) && obj instanceof Asset asset) {
obj = asset.getAsset();
}
property.setAccessible(true);
return property.get(obj);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
});
if (type.isEnum) {
var enumConstants = property.getType().getEnumConstants();
for (var enumConstant : enumConstants) {
if (!(enumConstant instanceof AssetEnum assetEnum)) {
throw new IllegalArgumentException("Property " + enumConstant.getClass().getName() + " does not implement AssetEnum");
}
var option = AssetOption.builder()
.value(assetEnum.getValue())
.displayName(assetEnum.getDisplayName())
.isDefaultValue(assetEnum.isDefaultValue())
.enumConstant(enumConstant)
.build();
builder.option(option);
}
}
if (type == PropertyType.CAPACITY) {
var capacityAnnotation = property.getAnnotation(Capacity.class);
builder.capacityAsSI(capacityAnnotation.si());
builder.capacityAsIEC(capacityAnnotation.iec());
}
return builder.build();
}
/**
* Determines the type of the property based on its field type.
*
* @param property The field representing the property.
* @return The type of the property.
* @throws IllegalArgumentException if the property type is unsupported.
*/
private static PropertyType determineType(Field property) {
if (property.getType() == String.class) {
return PropertyType.STRING;
} else if (property.isAnnotationPresent(Capacity.class)) {
return PropertyType.CAPACITY;
} else if (property.getType() == Integer.class || property.getType() == int.class || property.getType() == Long.class || property.getType() == long.class) {
return PropertyType.INTEGER;
} else if (property.getType() == Boolean.class || property.getType() == boolean.class) {
return PropertyType.BOOLEAN;
} else if (property.getType() == AssetCondition.class) {
return PropertyType.CONDITION;
} else if (property.getType() == ReadWrite.class) {
return PropertyType.READWRITE;
} else if (property.getType() == Build.class) {
return PropertyType.BUILD;
} else {
throw new IllegalArgumentException("Unsupported property type: " + property.getType());
}
}
/**
* Sets the value of the property on the given asset.
*
* @param asset The asset to set the property on.
* @param value The value to set for the property.
*/
public void setValue(Object asset, Object value) {
if (value == null && required) {
throw new IllegalArgumentException("Property '" + name + "' is required but received null value.");
}
setter.accept(asset, value);
}
/**
* Gets the value of the property from the given asset.
*
* @param asset The asset to get the property value from.
* @return The value of the property.
*/
@Nullable
public Object getValue(@Nullable Object asset) {
if (asset == null) {
return null;
}
return getter.apply(asset);
}
/**
* Renders the value of the property as a string.
*
* @return The rendered value as a string.
*/
@Nullable
public String renderValue(@Nullable Object asset) {
if (asset == null) {
return null;
}
var value = getValue(asset);
if (value == null) {
return "?";
} else if (type == PropertyType.BOOLEAN) {
return (boolean) value ? "Yes" : "No";
} else if (type == PropertyType.INTEGER || type == PropertyType.STRING) {
return value.toString();
} else if (type == PropertyType.CAPACITY) {
return convertCapacity((Long) value).toString();
} else if (type == PropertyType.BUILD) {
var build = (Build) value;
return build.getName();
} else if (type.isEnum) {
if (value instanceof AssetEnum assetEnum) {
return assetEnum.getDisplayName();
}
throw new IllegalArgumentException("Expected value to be an instance of AssetEnum, but got: " + value.getClass().getName());
} else {
return value.toString();
}
}
public CapacityInfo convertCapacity(Long value) {
if (value == null) {
return null;
}
if (type != PropertyType.CAPACITY) {
throw new IllegalStateException("Property '" + name + "' is not a capacity type.");
}
return CapacityInfo.of(value, capacityAsIEC, capacityAsSI);
}
public CapacityInfo asCapacity(@Nullable Object object) {
var value = getValue(object);
if (value == null) {
return null;
}
return convertCapacity((Long) value);
}
@Override
public String toString() {
var enumOptions = "";
if (type.isEnum) {
enumOptions = " [" + String.join(", ", options.stream().map(AssetOption::getValue).toList()) + "]";
}
return String.format("%s:%s (%s)%s", name, type.name(), displayName, enumOptions);
}
}