Skip to content

Commit 3968d13

Browse files
committed
optimize project
1 parent 06380d8 commit 3968d13

File tree

2 files changed

+217
-1
lines changed

2 files changed

+217
-1
lines changed

src/main/java/cn/lunadeer/reColorfulMap/commands/Clean.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package cn.lunadeer.reColorfulMap.commands;
22

33
import cn.lunadeer.reColorfulMap.ImageMapItem;
4-
import cn.lunadeer.reColorfulMap.utils.configuration.ConfigurationPart;
54
import org.bukkit.command.Command;
65
import org.bukkit.command.CommandSender;
76
import org.bukkit.command.TabExecutor;
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
package cn.lunadeer.reColorfulMap.utils.configuration;
2+
3+
import org.bukkit.configuration.file.YamlConfiguration;
4+
5+
import java.io.File;
6+
import java.lang.reflect.Field;
7+
import java.lang.reflect.Method;
8+
import java.util.*;
9+
10+
/**
11+
* Utility class for loading and saving configuration files.
12+
* <p>
13+
* This class uses reflection to read and write configuration files. Capable of reading and writing nested configuration parts.
14+
*/
15+
public class ConfigurationManager {
16+
17+
/**
18+
* Load the configuration file.
19+
*
20+
* @param clazz The configuration file class. The class should extend {@link ConfigurationFile}.
21+
* @param file The file to load.
22+
* @throws Exception If failed to load the file.
23+
*/
24+
public static void load(Class<? extends ConfigurationFile> clazz, File file) throws Exception {
25+
if (!file.exists()) {
26+
save(clazz, file);
27+
return;
28+
}
29+
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file);
30+
readConfigurationFile(yaml, clazz, null);
31+
yaml.options().width(250);
32+
yaml.save(file);
33+
}
34+
35+
/**
36+
* Load the configuration file and update the version field if needed.
37+
*
38+
* @param clazz The configuration file class. The class should extend {@link ConfigurationFile}.
39+
* @param file The file to load.
40+
* @param versionFieldName The name of the version field.
41+
* @throws Exception If failed to load the file.
42+
*/
43+
public static void load(Class<? extends ConfigurationFile> clazz, File file, String versionFieldName) throws Exception {
44+
Field versionField = clazz.getField(versionFieldName);
45+
int currentVersion = versionField.getInt(null);
46+
load(clazz, file);
47+
if (versionField.getInt(null) != currentVersion) {
48+
File backup = new File(file.getParentFile(), file.getName() + ".bak");
49+
if (backup.exists() && !backup.delete()) {
50+
throw new Exception("Failed to delete the backup configuration file.");
51+
}
52+
if (!file.renameTo(backup)) {
53+
throw new Exception("Failed to backup the configuration file.");
54+
}
55+
clazz.getField(versionFieldName).set(null, currentVersion);
56+
save(clazz, file);
57+
}
58+
}
59+
60+
/**
61+
* Save the configuration file.
62+
*
63+
* @param clazz The configuration file class. The class should extend {@link ConfigurationFile}.
64+
* @param file The file to save.
65+
* @throws Exception If failed to save the file.
66+
*/
67+
public static void save(Class<? extends ConfigurationFile> clazz, File file) throws Exception {
68+
createIfNotExist(file);
69+
YamlConfiguration yaml = new YamlConfiguration();
70+
yaml.options().width(250);
71+
writeConfigurationFile(yaml, clazz, null);
72+
yaml.save(file);
73+
}
74+
75+
private static void writeConfigurationFile(YamlConfiguration yaml, Class<? extends ConfigurationFile> clazz, String prefix) throws Exception {
76+
for (Field field : clazz.getFields()) {
77+
field.setAccessible(true);
78+
String key = camelToKebab(field.getName());
79+
if (prefix != null && !prefix.isEmpty()) {
80+
key = prefix + "." + key;
81+
}
82+
// if field is extending ConfigurationPart, recursively write the content
83+
if (ConfigurationPart.class.isAssignableFrom(field.getType())) {
84+
writeConfigurationPart(yaml, (ConfigurationPart) field.get(null), key);
85+
} else {
86+
yaml.set(key, field.get(null));
87+
}
88+
if (field.isAnnotationPresent(Comments.class)) {
89+
yaml.setComments(key, List.of(field.getAnnotation(Comments.class).value()));
90+
}
91+
}
92+
}
93+
94+
private static void writeConfigurationPart(YamlConfiguration yaml, ConfigurationPart obj, String key) throws Exception {
95+
for (Field field : obj.getClass().getFields()) {
96+
field.setAccessible(true);
97+
String newKey = key + "." + camelToKebab(field.getName());
98+
if (ConfigurationPart.class.isAssignableFrom(field.getType())) {
99+
writeConfigurationPart(yaml, (ConfigurationPart) field.get(obj), newKey);
100+
} else {
101+
yaml.set(newKey, field.get(obj));
102+
}
103+
if (field.isAnnotationPresent(Comments.class)) {
104+
yaml.setComments(newKey, List.of(field.getAnnotation(Comments.class).value()));
105+
}
106+
}
107+
}
108+
109+
private static void createIfNotExist(File file) throws Exception {
110+
if (file.exists()) return;
111+
if (!file.getParentFile().exists() && !file.getParentFile().mkdirs())
112+
throw new Exception("Failed to create %s directory.".formatted(file.getParentFile().getAbsolutePath()));
113+
if (!file.createNewFile()) throw new Exception("Failed to create %s file.".formatted(file.getAbsolutePath()));
114+
}
115+
116+
private static void readConfigurationFile(YamlConfiguration yaml, Class<? extends ConfigurationFile> clazz, String prefix) throws Exception {
117+
PrePostProcessInorder processes = getAndSortPrePostProcess(clazz);
118+
// execute methods with @PreProcess annotation
119+
for (Method method : processes.preProcessMethods) {
120+
method.invoke(null);
121+
}
122+
for (Field field : clazz.getFields()) {
123+
field.setAccessible(true);
124+
String key = camelToKebab(field.getName());
125+
if (prefix != null && !prefix.isEmpty()) {
126+
key = prefix + "." + key;
127+
}
128+
boolean missingKey = !yaml.contains(key);
129+
if (missingKey) {
130+
yaml.createSection(key);
131+
if (field.isAnnotationPresent(Comments.class)) {
132+
yaml.setComments(key, List.of(field.getAnnotation(Comments.class).value()));
133+
}
134+
}
135+
if (ConfigurationPart.class.isAssignableFrom(field.getType())) {
136+
readConfigurationPart(yaml, (ConfigurationPart) field.get(null), key);
137+
} else {
138+
if (missingKey) {
139+
yaml.set(key, field.get(null));
140+
} else {
141+
field.set(null, yaml.get(key));
142+
}
143+
}
144+
}
145+
// execute methods with @PostProcess annotation
146+
for (Method method : processes.postProcessMethods) {
147+
method.invoke(null);
148+
}
149+
}
150+
151+
private static void readConfigurationPart(YamlConfiguration yaml, ConfigurationPart obj, String key) throws Exception {
152+
for (Field field : obj.getClass().getFields()) {
153+
field.setAccessible(true);
154+
String newKey = key + "." + camelToKebab(field.getName());
155+
boolean missingKey = !yaml.contains(newKey);
156+
if (missingKey) {
157+
yaml.createSection(newKey);
158+
if (field.isAnnotationPresent(Comments.class)) {
159+
yaml.setComments(newKey, List.of(field.getAnnotation(Comments.class).value()));
160+
}
161+
}
162+
if (ConfigurationPart.class.isAssignableFrom(field.getType())) {
163+
readConfigurationPart(yaml, (ConfigurationPart) field.get(obj), newKey);
164+
} else {
165+
if (missingKey) {
166+
yaml.set(newKey, field.get(obj));
167+
} else {
168+
field.set(obj, yaml.get(newKey));
169+
}
170+
}
171+
}
172+
}
173+
174+
/**
175+
* Converts a camelCase string to kebab-case.
176+
*
177+
* @param camel The camelCase string.
178+
* @return The kebab-case string.
179+
*/
180+
private static String camelToKebab(String camel) {
181+
return camel.replaceAll("([a-z])([A-Z]+)", "$1-$2").toLowerCase();
182+
}
183+
184+
private static class PrePostProcessInorder {
185+
public List<Method> preProcessMethods = new ArrayList<>();
186+
public List<Method> postProcessMethods = new ArrayList<>();
187+
}
188+
189+
/**
190+
* Get methods with @PreProcess and @PostProcess annotations and sort them by priority.
191+
* <p>
192+
* The methods with higher priority will be executed first.
193+
*
194+
* @param clazz The configuration file class.
195+
* @return A {@link PrePostProcessInorder} object containing the sorted methods.
196+
*/
197+
private static PrePostProcessInorder getAndSortPrePostProcess(Class<? extends ConfigurationFile> clazz) {
198+
Map<Method, Integer> preProcessMethodsWithPriority = new HashMap<>();
199+
Map<Method, Integer> postProcessMethodsWithPriority = new HashMap<>();
200+
for (Method method : clazz.getMethods()) {
201+
if (method.isAnnotationPresent(PreProcess.class)) {
202+
preProcessMethodsWithPriority.put(method, method.getAnnotation(PreProcess.class).priority());
203+
}
204+
if (method.isAnnotationPresent(PostProcess.class)) {
205+
postProcessMethodsWithPriority.put(method, method.getAnnotation(PostProcess.class).priority());
206+
}
207+
}
208+
PrePostProcessInorder result = new PrePostProcessInorder();
209+
// sort methods by priority ascending
210+
result.preProcessMethods = new ArrayList<>(preProcessMethodsWithPriority.keySet());
211+
result.preProcessMethods.sort(Comparator.comparingInt(preProcessMethodsWithPriority::get));
212+
result.postProcessMethods = new ArrayList<>(postProcessMethodsWithPriority.keySet());
213+
result.postProcessMethods.sort(Comparator.comparingInt(postProcessMethodsWithPriority::get));
214+
return result;
215+
}
216+
217+
}

0 commit comments

Comments
 (0)