host.anzo.commons.config.ConfigLoader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of commons-core Show documentation
Show all versions of commons-core Show documentation
Commons library to make me happy.
package host.anzo.commons.config;
import com.google.common.collect.ImmutableList;
import com.sun.jna.platform.FileUtils;
import host.anzo.classindex.ClassIndex;
import host.anzo.commons.config.annotation.ConfigAfterLoad;
import host.anzo.commons.config.annotation.ConfigComments;
import host.anzo.commons.config.annotation.ConfigFile;
import host.anzo.commons.config.annotation.ConfigProperty;
import host.anzo.core.startup.IReloadable;
import host.anzo.core.startup.Reloadable;
import host.anzo.core.startup.StartupComponent;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.TextStringBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.*;
import java.util.stream.Stream;
/**
* @author ANZO
* @since 02.04.2017
*/
@Slf4j
@Reloadable(name = "all", group = "config")
@StartupComponent("Configure")
public final class ConfigLoader implements IReloadable {
@Getter(lazy = true)
private static final ConfigLoader instance = new ConfigLoader();
private static final Set VAR_NAMES_CACHE = new HashSet<>();
public ConfigLoader() {
loadConfigs();
cleanupConfigs();
}
private void loadConfigs() {
for (Class> clazz : ClassIndex.getAnnotated(ConfigFile.class)) {
boolean loadConfig = false;
final ConfigFile annotation = clazz.getAnnotation(ConfigFile.class);
if (annotation.loadForPackages().length > 0) {
for(String classPath : annotation.loadForPackages()) {
if (!StringUtils.isEmpty(classPath)) {
final URL url = getClass().getClassLoader().getResource(classPath.replace(".", "/"));
if (url != null) {
loadConfig = true;
break;
}
}
}
}
else {
loadConfig = true;
}
if (!loadConfig) {
continue;
}
final File file = getConfigFilePath(annotation.name()).toFile();
if (!file.exists() && file.isDirectory()) {
file.mkdirs();
}
if (!file.exists()) {
buildConfig(clazz);
}
else {
updateConfig(clazz);
}
loadConfig(clazz);
}
}
/**
* Cleanup config file from unused properties
*/
private void cleanupConfigs() {
final List> configClasses = ImmutableList.copyOf(ClassIndex.getAnnotated(ConfigFile.class).iterator());
try (Stream walk = Files.walk(Paths.get(getConfigFolder(), "config"))) {
final List configFiles = walk.filter(Files::isRegularFile).filter(x -> x.toString().endsWith(".properties"))
.toList();
for (Path configFilePath : configFiles) {
if (configClasses.stream().noneMatch(clazz -> {
final ConfigFile annotation = clazz.getAnnotation(ConfigFile.class);
final Path filePath = getConfigFilePath(annotation.name());
try {
return Files.isSameFile(configFilePath, filePath);
} catch (IOException e) {
throw new RuntimeException("Error checking equality for config file " + configFilePath, e);
}
})) {
if (FileUtils.getInstance().hasTrash()) {
FileUtils.getInstance().moveToTrash(configFilePath.toFile());
log.warn("Moved to trash [{}] config file due config loader didn't exist anymore.", configFilePath);
}
else {
Files.delete(configFilePath);
log.warn("Removed [{}] config file due config loader didn't exist anymore.", configFilePath);
}
}
}
}
catch (Exception e) {
log.error("Error while cleanupConfigs()", e);
}
}
private void updateConfig(@NotNull Class> clazz) {
final Properties properties = new Properties();
final Path filePath = getConfigFilePath(clazz.getAnnotation(ConfigFile.class).name());
try(InputStream input = Files.newInputStream(filePath)) {
properties.load(input);
}
catch (IOException ex) {
log.error("Error while calling loadConfig", ex);
}
final TextStringBuilder newPropertiesText = new TextStringBuilder();
newPropertiesText.appendln("");
boolean isNewPropertyExists = false;
for (Field field : clazz.getDeclaredFields()) {
final ConfigProperty annotation = field.getAnnotation(ConfigProperty.class);
if (!annotation.isLoadFromFile()) {
continue;
}
final String propertyValue = properties.getProperty(annotation.name());
if (propertyValue == null && !annotation.isMap()) {
isNewPropertyExists = true;
newPropertiesText.appendNewLine();
newPropertiesText.appendln(generateFieldConfig(clazz, field).trim());
log.warn("Updated '{}' config with new field '{}'", filePath, annotation.name());
}
}
if (isNewPropertyExists) {
try {
Files.write(filePath, newPropertiesText.toString().getBytes(), StandardOpenOption.APPEND);
}
catch (Exception e) {
log.error("Error while writing config update", e);
}
}
}
private void buildConfig(@NotNull Class> clazz) {
final Path filePath = getConfigFilePath(clazz.getAnnotation(ConfigFile.class).name());
log.info("Generated '{}'", filePath);
try {
Files.deleteIfExists(filePath);
Files.createDirectories(filePath.getParent());
} catch (IOException ex) {
log.error("Error while buildConfig()", ex);
return;
}
final TextStringBuilder out = new TextStringBuilder();
for (Field field : clazz.getDeclaredFields()) {
final String configField = generateFieldConfig(clazz, field);
if (StringUtils.isNotEmpty(configField)) {
out.appendln(configField);
}
}
if (out.trim().isEmpty()) {
return;
}
try {
Files.write(filePath, out.toString().getBytes(), StandardOpenOption.CREATE);
} catch (IOException ex) {
log.error("Error while writing config file: {}", filePath, ex);
}
}
private @Nullable String generateFieldConfig(@NotNull Class> clazz, @NotNull Field field) {
final ConfigComments configComments = field.getAnnotation(ConfigComments.class);
final ConfigProperty configProperty = field.getAnnotation(ConfigProperty.class);
if (configProperty == null) {
throw new RuntimeException("ConfigProperty annotation not found on field: " + field.getName());
}
if (!configProperty.isLoadFromFile()) {
return null;
}
final TextStringBuilder out = new TextStringBuilder();
if (configComments != null) {
for (String txt : configComments.comment()) {
out.appendln("# " + txt);
}
}
final String varCacheName = clazz.getSimpleName() + "." + configProperty.name();
if (VAR_NAMES_CACHE.contains(varCacheName)) {
log.warn("Config property name [{}] already defined in class [{}]!", configProperty.name(), clazz.getSimpleName());
}
else {
VAR_NAMES_CACHE.add(varCacheName);
}
if (configProperty.isMap()) {
for (String value : configProperty.values()) {
out.appendln(value);
}
}
else {
out.appendln(configProperty.name() + " = " + configProperty.value());
}
return out.toString();
}
private void loadConfig(@NotNull Class> clazz) {
final Properties properties = new Properties();
final Path filePath = getConfigFilePath(clazz.getAnnotation(ConfigFile.class).name());
log.info("Loading config file: {}", filePath);
try(InputStream input = Files.newInputStream(filePath)) {
properties.load(input);
}
catch (IOException ex) {
log.error("Error while calling loadConfig", ex);
}
try {
final Object configObject = clazz.getDeclaredConstructor().newInstance();
for (Field field : clazz.getFields()) {
final ConfigProperty configProperty = field.getAnnotation(ConfigProperty.class);
if (configProperty == null) {
continue;
}
if (!configProperty.isLoadFromFile()) {
continue;
}
if (!Modifier.isStatic(field.getModifiers())
|| Modifier.isFinal(field.getModifiers())) {
log.warn("Invalid modifiers for {} (must be static and final)", field);
continue;
}
setConfigValue(configObject, field, properties, configProperty);
}
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(ConfigAfterLoad.class)) {
if (method.trySetAccessible()) {
method.invoke(configObject);
}
}
}
} catch (Exception e) {
log.error("Error while initializing config object", e);
}
}
@SuppressWarnings("unchecked")
private void setConfigValue(Object object, @NotNull Field field, @NotNull Properties properties, @NotNull ConfigProperty annotation) {
final String propertyValue = properties.getProperty(annotation.name(), annotation.value());
try {
if (!field.canAccess(null)) {
field.setAccessible(true);
}
if (field.getType().isAssignableFrom(Map.class)
|| field.getType().isAssignableFrom(EnumMap.class)) {
final Map