mirror of
https://athene2.informatik.unibw-muenchen.de/progproj/gruppen-ht24/Gruppe-02.git
synced 2025-10-04 12:00:32 +02:00
added contents
This commit is contained in:
286
Projekte/common/src/main/java/pp/util/config/Config.java
Normal file
286
Projekte/common/src/main/java/pp/util/config/Config.java
Normal file
@@ -0,0 +1,286 @@
|
||||
////////////////////////////////////////
|
||||
// Programming project code
|
||||
// UniBw M, 2022, 2023, 2024
|
||||
// www.unibw.de/inf2
|
||||
// (c) Mark Minas (mark.minas@unibw.de)
|
||||
////////////////////////////////////////
|
||||
|
||||
package pp.util.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.lang.System.Logger;
|
||||
import java.lang.System.Logger.Level;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* Abstract base class for representing configurations that can be read from properties files.
|
||||
* Subclasses can define fields annotated with {@link Property} to specify the configuration keys
|
||||
* and optionally {@link Separator} to specify custom separators for array values.
|
||||
*/
|
||||
public abstract class Config {
|
||||
private static final String BLANK_SEQ = " *";
|
||||
|
||||
/**
|
||||
* Annotation for specifying the property key for a field.
|
||||
*/
|
||||
@Retention(RUNTIME)
|
||||
@Target(FIELD)
|
||||
@Documented
|
||||
protected @interface Property {
|
||||
/**
|
||||
* The key of the property.
|
||||
*/
|
||||
String value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotation for specifying a custom separator for array values.
|
||||
*/
|
||||
@Retention(RUNTIME)
|
||||
@Target(FIELD)
|
||||
@Documented
|
||||
protected @interface Separator {
|
||||
/**
|
||||
* The separator for array values.
|
||||
*/
|
||||
String value();
|
||||
}
|
||||
|
||||
private static final Logger LOGGER = System.getLogger(Config.class.getName());
|
||||
private static final Map<Class<?>, Function<String, ?>> CONVERTER_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
CONVERTER_MAP.put(String.class, x -> x);
|
||||
CONVERTER_MAP.put(byte.class, Byte::parseByte);
|
||||
CONVERTER_MAP.put(short.class, Short::parseShort);
|
||||
CONVERTER_MAP.put(int.class, Integer::parseInt);
|
||||
CONVERTER_MAP.put(long.class, Long::parseLong);
|
||||
CONVERTER_MAP.put(boolean.class, Boolean::parseBoolean);
|
||||
CONVERTER_MAP.put(float.class, Float::parseFloat);
|
||||
CONVERTER_MAP.put(double.class, Double::parseDouble);
|
||||
CONVERTER_MAP.put(Byte.class, Byte::parseByte);
|
||||
CONVERTER_MAP.put(Short.class, Short::parseShort);
|
||||
CONVERTER_MAP.put(Integer.class, Integer::parseInt);
|
||||
CONVERTER_MAP.put(Long.class, Long::parseLong);
|
||||
CONVERTER_MAP.put(Boolean.class, Boolean::parseBoolean);
|
||||
CONVERTER_MAP.put(Float.class, Float::parseFloat);
|
||||
CONVERTER_MAP.put(Double.class, Double::parseDouble);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the specified properties file and sets the values of this config using {@link #readFrom(Properties)}.
|
||||
*
|
||||
* @param file the properties file to read
|
||||
* @throws IOException if an I/O error occurs
|
||||
* @see #readFrom(Properties)
|
||||
*/
|
||||
public void readFrom(File file) throws IOException {
|
||||
try (Reader reader = new FileReader(file)) {
|
||||
final Properties properties = new Properties();
|
||||
properties.load(reader);
|
||||
readFrom(properties);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the values of fields annotated with {@link Property} using the specified properties.
|
||||
* Array fields can be split into components using the default separator (",") or a custom separator
|
||||
* specified with {@link Separator}.
|
||||
*
|
||||
* @param props the properties to read from
|
||||
*/
|
||||
public void readFrom(Properties props) {
|
||||
for (Class<?> clazz = getClass(); Config.class.isAssignableFrom(clazz); clazz = clazz.getSuperclass()) {
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
final Property keyAnnot = field.getAnnotation(Property.class);
|
||||
if (keyAnnot != null && props.containsKey(keyAnnot.value())) {
|
||||
try {
|
||||
final String text = props.getProperty(keyAnnot.value());
|
||||
final Object value = createValue(text, field);
|
||||
setField(field, value);
|
||||
}
|
||||
catch (IllegalAccessException ex) {
|
||||
LOGGER.log(Level.ERROR, "Cannot access " + field, ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the specified properties file and sets the values of this config if the file exists,
|
||||
* otherwise uses default values. This method is a convenience version of {@link #readFrom(File)}
|
||||
* that checks the existence of the specified file and does nothing if the file does not exist.
|
||||
*
|
||||
* @param file the properties file to read, if it exists
|
||||
*/
|
||||
public void readFromIfExists(File file) {
|
||||
if (!file.exists()) {
|
||||
LOGGER.log(Level.INFO, "There is no config file {0}; using default configuration", //NON-NLS
|
||||
file.getAbsolutePath());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
readFrom(file);
|
||||
LOGGER.log(Level.INFO, "Successfully read config from {0}", file.getAbsolutePath()); //NON-NLS
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOGGER.log(Level.WARNING, "Cannot read config file " + file.getAbsolutePath(), e); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string value from the properties file into an object that can be assigned to the specified field.
|
||||
* For array fields, the string value is first split into components.
|
||||
*
|
||||
* @param value the string value to convert
|
||||
* @param field the field to set with the converted value
|
||||
* @return an object of the appropriate type for the field
|
||||
*/
|
||||
private Object createValue(String value, Field field) {
|
||||
if (!field.getType().isArray())
|
||||
return convertToType(value, field.getType());
|
||||
// the field is an array
|
||||
final Separator sepAnn = field.getDeclaredAnnotation(Separator.class);
|
||||
final String sep = sepAnn == null ? "," : sepAnn.value();
|
||||
final String[] split = value.split(BLANK_SEQ + sep + BLANK_SEQ, -1);
|
||||
final Object array = Array.newInstance(field.getType().componentType(), split.length);
|
||||
for (int i = 0; i < split.length; i++)
|
||||
Array.set(array, i, convertToType(split[i], field.getType().componentType()));
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string value into an object of the specified type.
|
||||
*
|
||||
* @param value the string value to convert
|
||||
* @param targetType the target type to convert to
|
||||
* @return an object of the specified type
|
||||
*/
|
||||
protected Object convertToType(String value, Class<?> targetType) {
|
||||
Function<String, ?> handler = CONVERTER_MAP.get(targetType);
|
||||
if (handler != null)
|
||||
return handler.apply(value);
|
||||
throw new IllegalArgumentException("Cannot translate " + value + " to " + targetType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the configuration object, including all properties and their values.
|
||||
*
|
||||
* @return a string representation of the configuration object
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
final List<String> propertyStrings = getPropertyStrings();
|
||||
propertyStrings.sort(String.CASE_INSENSITIVE_ORDER);
|
||||
return "[\n" + String.join(",\n", propertyStrings) + "\n]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all property strings of the configuration object.
|
||||
*
|
||||
* @return a list of property strings
|
||||
*/
|
||||
private List<String> getPropertyStrings() {
|
||||
final List<String> propertyStrings = new ArrayList<>();
|
||||
for (Class<?> clazz = getClass(); Config.class.isAssignableFrom(clazz); clazz = clazz.getSuperclass()) {
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
final String stringRepresentation = getStringRepresentation(field);
|
||||
if (stringRepresentation != null)
|
||||
propertyStrings.add(stringRepresentation);
|
||||
}
|
||||
}
|
||||
return propertyStrings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the string representation of a field annotated with {@link Property}.
|
||||
*
|
||||
* @param field the field to retrieve the string representation for
|
||||
* @return the string representation of the field, or null if the field is not annotated with {@link Property}
|
||||
*/
|
||||
private String getStringRepresentation(Field field) {
|
||||
final Property keyAnnotation = field.getAnnotation(Property.class);
|
||||
if (keyAnnotation != null) {
|
||||
try {
|
||||
final Object fieldValue = getField(field);
|
||||
final String valueString = asString(fieldValue);
|
||||
return keyAnnotation.value() + " -> " + field.getName() + " = " + valueString;
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
LOGGER.log(Level.ERROR, "Cannot access " + field, e); //NON-NLS
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an object to its string representation. For arrays, string representations of their components are produced.
|
||||
*
|
||||
* @param value the object to convert
|
||||
* @return the string representation of the object
|
||||
*/
|
||||
private String asString(Object value) {
|
||||
if (value == null)
|
||||
return "null"; //NON-NLS
|
||||
if (!value.getClass().isArray())
|
||||
return value.toString();
|
||||
final int length = Array.getLength(value);
|
||||
final List<String> components = new ArrayList<>(length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
final Object component = Array.get(value, i);
|
||||
components.add(asString(component));
|
||||
}
|
||||
return "{" + String.join(", ", components) + "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a field, making it accessible if necessary.
|
||||
*
|
||||
* @param field the field to set
|
||||
* @param value the value to set
|
||||
* @throws IllegalAccessException if the field cannot be accessed
|
||||
*/
|
||||
private void setField(Field field, Object value) throws IllegalAccessException {
|
||||
boolean inaccessible = !field.canAccess(this);
|
||||
if (inaccessible)
|
||||
field.setAccessible(true);
|
||||
field.set(this, value);
|
||||
if (inaccessible)
|
||||
field.setAccessible(false);
|
||||
LOGGER.log(Level.TRACE, "Set {0} to {1}", field, value); //NON-NLS
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of a field, making it accessible if necessary.
|
||||
*
|
||||
* @param field the field to retrieve the value from
|
||||
* @return the value of the field
|
||||
* @throws IllegalAccessException if the field cannot be accessed
|
||||
*/
|
||||
private Object getField(Field field) throws IllegalAccessException {
|
||||
boolean inaccessible = !field.canAccess(this);
|
||||
if (inaccessible)
|
||||
field.setAccessible(true);
|
||||
final Object value = field.get(this);
|
||||
if (inaccessible)
|
||||
field.setAccessible(false);
|
||||
return value;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user