ConfigFieldInfo.java
package com.cloudforge.core.config;
import com.cloudforge.core.annotation.ConfigField;
import com.cloudforge.core.annotation.FieldTag;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
/**
* Runtime metadata for a configuration field discovered via introspection.
*
* <p>Encapsulates all information from {@link ConfigField} annotation plus
* reflection metadata needed for prompting, validation, and value assignment.</p>
*
* @param fieldName the Java field name
* @param displayName the user-friendly display name
* @param description the field description for user help
* @param category the configuration category for grouping
* @param visibleWhen the visibility condition expression
* @param dependsOn the field this depends on
* @param required whether the field is required
* @param example an example value for user guidance
* @param allowedValues array of allowed values (for constrained choices)
* @param min the minimum value for numeric fields
* @param max the maximum value for numeric fields
* @param pattern the regex pattern for string validation
* @param defaultFrom the ApplicationSpec method to get default value from
* @param sensitive whether this field contains sensitive data
* @param sourceConfig the field containing the source location for sensitive data
* @param tags the field tags for categorization and filtering
* @param validators the custom validator class names
* @param order the display order for sorting fields
* @param type the Java type of the field
* @param field the reflected Field object
* @since 3.0.0
*/
public record ConfigFieldInfo(
String fieldName,
String displayName,
String description,
String category,
String visibleWhen,
String dependsOn,
boolean required,
String example,
String[] allowedValues,
double min,
double max,
String pattern,
String defaultFrom,
boolean sensitive,
String sourceConfig,
FieldTag[] tags,
String[] validators,
int order,
Class<?> type,
Field field
) {
/**
* Creates ConfigFieldInfo from a field with @ConfigField annotation.
*/
public static ConfigFieldInfo from(Field field) {
ConfigField annotation = field.getAnnotation(ConfigField.class);
if (annotation == null) {
throw new IllegalArgumentException("Field must have @ConfigField annotation: " + field.getName());
}
field.setAccessible(true);
return new ConfigFieldInfo(
field.getName(),
annotation.displayName(),
annotation.description(),
annotation.category(),
annotation.visibleWhen(),
annotation.dependsOn(),
annotation.required(),
annotation.example(),
annotation.allowedValues(),
annotation.min(),
annotation.max(),
annotation.pattern(),
annotation.defaultFrom(),
annotation.sensitive(),
annotation.sourceConfig(),
annotation.tags(),
annotation.validators(),
annotation.order(),
field.getType(),
field
);
}
/**
* Gets the current value of this field from the config object.
*/
public Object getValue(Object config) {
try {
return field.get(config);
} catch (IllegalAccessException e) {
throw new RuntimeException("Failed to get field value: " + fieldName, e);
}
}
/**
* Sets the value of this field in the config object.
*/
public void setValue(Object config, Object value) {
try {
field.set(config, value);
} catch (IllegalAccessException e) {
throw new RuntimeException("Failed to set field value: " + fieldName, e);
}
}
/**
* Checks if this field has a specific tag.
*/
public boolean hasTag(FieldTag tag) {
return Arrays.asList(tags).contains(tag);
}
/**
* Gets all tags as a list.
*/
public List<FieldTag> tagList() {
return Arrays.asList(tags);
}
/**
* Checks if this field is visible based on the current configuration.
*
* @param appSpec the application spec (may be null)
* @param config the deployment config object
* @return true if the field should be visible, false otherwise
*/
public boolean isVisible(Object appSpec, Object config) {
if (visibleWhen.equals("always") || visibleWhen.isEmpty()) {
return true;
}
try {
VisibilityExpressionEvaluator evaluator = new VisibilityExpressionEvaluator(
(com.cloudforge.core.interfaces.ApplicationSpec) appSpec,
config,
visibleWhen
);
return evaluator.evaluate();
} catch (Exception e) {
// Log warning but don't fail - default to visible
System.err.println("Warning: Failed to evaluate visibility expression '" + visibleWhen +
"' for field '" + fieldName + "': " + e.getMessage());
return true;
}
}
/**
* Validates the value according to field constraints and custom validators.
*/
public ValidationResult validate(Object value, Object config) {
// Basic validation
if (required && value == null) {
return ValidationResult.error("Field '" + displayName + "' is required");
}
// Numeric range validation
if (value instanceof Number num) {
double d = num.doubleValue();
if (d < min) {
return ValidationResult.error("Field '" + displayName + "' must be >= " + min);
}
if (d > max) {
return ValidationResult.error("Field '" + displayName + "' must be <= " + max);
}
}
// Allowed values validation
if (allowedValues.length > 0 && value != null) {
String strValue = value.toString();
boolean found = false;
for (String allowed : allowedValues) {
if (allowed.equals(strValue)) {
found = true;
break;
}
}
if (!found) {
return ValidationResult.error("Field '" + displayName + "' must be one of: " +
String.join(", ", allowedValues));
}
}
// Pattern validation
if (!pattern.isEmpty() && value != null) {
String strValue = value.toString();
if (!strValue.matches(pattern)) {
return ValidationResult.error("Field '" + displayName + "' does not match required pattern: " + pattern);
}
}
// Custom validator execution
if (validators.length > 0 && config != null) {
for (String validatorName : validators) {
try {
// Try to find validator in com.cloudforge.core.config package
Class<?> validatorClass = Class.forName("com.cloudforge.core.config." + validatorName);
FieldValidator validator = (FieldValidator) validatorClass.getDeclaredConstructor().newInstance();
ValidationResult result = validator.validate(this, value, config);
if (result.isError()) {
return result;
}
} catch (ClassNotFoundException e) {
System.err.println("Warning: Validator not found: " + validatorName);
} catch (Exception e) {
System.err.println("Warning: Failed to execute validator " + validatorName + ": " + e.getMessage());
}
}
}
return ValidationResult.ok();
}
}