org.rx.core.YamlConfiguration Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rxlib Show documentation
Show all versions of rxlib Show documentation
A set of utilities for Java
package org.rx.core;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONReader;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.rx.annotation.ErrorCode;
import org.rx.exception.ApplicationException;
import org.rx.exception.InvalidException;
import org.rx.io.FileStream;
import org.rx.io.FileWatcher;
import org.rx.io.Files;
import org.yaml.snakeyaml.Yaml;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static org.rx.core.Extends.as;
import static org.rx.core.Extends.values;
@Slf4j
public class YamlConfiguration implements EventPublisher {
@RequiredArgsConstructor
@Getter
public static class ChangedEventArgs extends EventArgs {
private static final long serialVersionUID = -1217316266335592369L;
final String filePath;
}
public static final YamlConfiguration RX_CONF = new YamlConfiguration(Constants.DEFAULT_CONFIG_FILES);
public static Map loadYaml(String... fileNames) {
return loadYaml(Linq.from(fileNames).selectMany(p -> {
File file = new File(p);
if (file.exists()) {
return Arrays.toList(new FileStream(file).getReader());
}
Linq resources = Reflects.getResources(p);
if (resources.any()) {
return resources.reverse();
}
return resources;
}).toList());
}
public static Map loadYaml(List streams) {
Map result = new LinkedHashMap<>();
if (CollectionUtils.isEmpty(streams)) {
return result;
}
Yaml yaml = new Yaml();
for (Object data : Linq.from(streams).selectMany(yaml::loadAll)) {
Map sub = (Map) data;
fill(sub, result);
}
return result;
}
private static void fill(Map child, Map parent) {
if (child == null) {
return;
}
for (Map.Entry entry : child.entrySet()) {
Map next;
if ((next = as(entry.getValue(), Map.class)) == null) {
parent.put(entry.getKey(), entry.getValue());
continue;
}
Map nextAll = (Map) parent.get(entry.getKey());
if (nextAll == null) {
parent.put(entry.getKey(), next);
continue;
}
fill(next, nextAll);
}
}
public final Delegate onChanged = Delegate.create();
final String[] fileNames;
@Getter
final Map yaml;
String outputFile;
FileWatcher watcher;
public YamlConfiguration(@NonNull String... fileNames) {
yaml = loadYaml(this.fileNames = fileNames);
}
public YamlConfiguration enableWatch() {
if (fileNames.length == 0) {
throw new InvalidException("Empty loaded fileNames");
}
return enableWatch(fileNames[fileNames.length - 1]);
}
public synchronized YamlConfiguration enableWatch(@NonNull String outputFile) {
if (watcher != null) {
throw new InvalidException("Already watched");
}
if (!yaml.isEmpty()) {
try (FileStream fs = new FileStream(outputFile)) {
fs.setPosition(0);
fs.writeString(new Yaml().dumpAsMap(yaml));
fs.flip();
}
}
watcher = new FileWatcher(Files.getFullPath(this.outputFile = outputFile), p -> p.toString().equals(this.outputFile));
watcher.onChanged.combine((s, e) -> {
String filePath = e.getPath().toString();
log.info("Config changing {} {} -> {}", e.isCreate(), filePath, yaml);
synchronized (this) {
yaml.clear();
if (!e.isDelete()) {
write(filePath);
}
}
log.info("Config changed {} {} -> {}", e.isCreate(), filePath, yaml);
raiseEvent(onChanged, new ChangedEventArgs(filePath));
});
return this;
}
public synchronized void raiseChange() {
raiseChange(outputFile);
}
public synchronized void raiseChange(String filePath) {
if (filePath == null) {
return;
}
File f = new File(filePath);
if (!f.exists()) {
log.warn("File not found {}", filePath);
return;
}
write(filePath);
log.info("Config changed {} -> {}", filePath, yaml);
raiseEvent(onChanged, new ChangedEventArgs(filePath));
}
public synchronized YamlConfiguration write(@NonNull String fileName) {
String[] clone = fileNames.clone();
yaml.putAll(loadYaml(Arrays.add(clone, fileName)));
return this;
}
public T read(String key, T defaultVal) {
Object val = readAs(key, Object.class);
return val != null ? (T) val : defaultVal;
}
public T readAs(Type type) {
return readAs(null, type, false);
}
public T readAs(String key, Type type) {
return readAs(key, type, false);
}
@ErrorCode("keyError")
@ErrorCode("partialKeyError")
public synchronized T readAs(String key, Type type, boolean throwOnEmpty) {
Map tmp = yaml;
if (key == null) {
return convert(tmp, type);
}
Object val;
if ((val = tmp.get(key)) != null) {
return convert(val, type);
}
StringBuilder buf = new StringBuilder();
String[] splits = Strings.split(key, Constants.CONFIG_KEY_SPLITS);
int c = splits.length - 1;
for (int i = 0; i <= c; i++) {
if (buf.length() > 0) {
buf.append(Constants.CONFIG_KEY_SPLITS);
}
String k = buf.append(splits[i]).toString();
if ((val = tmp.get(k)) == null) {
continue;
}
if (i == c) {
return convert(val, type);
}
if ((tmp = as(val, Map.class)) == null) {
throw new ApplicationException("partialKeyError", values(k, type));
}
buf.setLength(0);
}
if (throwOnEmpty) {
throw new ApplicationException("keyError", values(key, type));
}
return null;
}
T convert(Object p, Type type) {
if (type == null) {
return null;
}
Map map = as(p, Map.class);
boolean isProp = map != null;
if (isProp && type.equals(Map.class)) {
return (T) map;
}
Class clz = as(type, Class.class);
if (isProp || clz == null) {
//new Yaml().loadAs() 不支持嵌套泛型
// return new JSONObject(map).to(type, JSONReader.Feature.SupportClassForName);
return JSON.parseObject(JSON.toJSONString(p), type, JSONReader.Feature.SupportClassForName);
}
return Reflects.changeType(p, clz);
}
}