226 lines
8.8 KiB
Java
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);
|
|
}
|
|
}
|